summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Hanzel <mhanzel@gitlab.com>2019-07-24 14:27:53 +0200
committerMartin Hanzel <mhanzel@gitlab.com>2019-07-24 14:27:53 +0200
commit0b8b0c718bef1e782b151072f2e4decf723d02c7 (patch)
treeb10660598024bad2bac52dd40cfd01adfa9efba4
parenteb429bce1fb994aefe992ee1e187c7d88fbe6c37 (diff)
parenteebc9cb67ee2ff4b19ab31f9938a1c918000a70f (diff)
downloadgitlab-ce-0b8b0c718bef1e782b151072f2e4decf723d02c7.tar.gz
Merge branch 'master' into mh/editor-indents
-rw-r--r--.gitlab/CODEOWNERS6
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml49
-rw-r--r--.gitlab/ci/memory.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml9
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml12
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml33
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml2
-rw-r--r--.gitlab/issue_templates/Documentation.md2
-rw-r--r--.gitlab/merge_request_templates/Change documentation location.md2
-rw-r--r--.gitlab/merge_request_templates/Database changes.md13
-rw-r--r--.haml-lint.yml4
-rw-r--r--.mdlrc3
-rw-r--r--.mdlrc.style22
-rw-r--r--.rubocop.yml4
-rw-r--r--CHANGELOG.md275
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--Gemfile17
-rw-r--r--Gemfile.lock159
-rw-r--r--README.md2
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/api.js6
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js4
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js4
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_metrics.js24
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue11
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue216
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue334
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue5
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue8
-rw-r--r--app/assets/javascripts/boards/config_toggle.js1
-rw-r--r--app/assets/javascripts/boards/ee_functions.js7
-rw-r--r--app/assets/javascripts/boards/index.js50
-rw-r--r--app/assets/javascripts/boards/mixins/modal_footer.js1
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js37
-rw-r--r--app/assets/javascripts/boards/services/board_service.js16
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js38
-rw-r--r--app/assets/javascripts/boards/toggle_focus.js1
-rw-r--r--app/assets/javascripts/commons/index.js3
-rw-r--r--app/assets/javascripts/commons/nav/user_merge_requests.js67
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/confidential_merge_request/components/project_form_group.vue12
-rw-r--r--app/assets/javascripts/create_merge_request_dropdown.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js10
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue19
-rw-r--r--app/assets/javascripts/filterable_list.js6
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue2
-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.js8
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js4
-rw-r--r--app/assets/javascripts/issuable_index.js17
-rw-r--r--app/assets/javascripts/issuable_init_bulk_update_sidebar.js19
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue7
-rw-r--r--app/assets/javascripts/issue_show/components/locked_warning.vue21
-rw-r--r--app/assets/javascripts/issue_show/components/pinned_links.vue31
-rw-r--r--app/assets/javascripts/labels_select.js3
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue111
-rw-r--r--app/assets/javascripts/monitoring/components/charts/column.vue23
-rw-r--r--app/assets/javascripts/monitoring/components/charts/empty_chart.vue41
-rw-r--r--app/assets/javascripts/monitoring/components/charts/single_stat.vue29
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue67
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue97
-rw-r--r--app/assets/javascripts/monitoring/components/graph_group.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue71
-rw-r--r--app/assets/javascripts/monitoring/constants.js2
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js15
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js2
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js7
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js27
-rw-r--r--app/assets/javascripts/monitoring/utils.js24
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue11
-rw-r--r--app/assets/javascripts/notes/components/discussion_actions.vue25
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue2
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js6
-rw-r--r--app/assets/javascripts/notes/stores/actions.js15
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js4
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue6
-rw-r--r--app/assets/javascripts/projects/projects_filterable_list.js7
-rw-r--r--app/assets/javascripts/projects_list.js4
-rw-r--r--app/assets/javascripts/registry/components/app.vue51
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue10
-rw-r--r--app/assets/javascripts/registry/components/svg_message.vue6
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue9
-rw-r--r--app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue2
-rw-r--r--app/assets/javascripts/reports/components/issue_status_icon.vue2
-rw-r--r--app/assets/javascripts/reports/components/report_item.vue9
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue4
-rw-r--r--app/assets/javascripts/reports/components/summary_row.vue12
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue186
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue3
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue7
-rw-r--r--app/assets/javascripts/repository/index.js50
-rw-r--r--app/assets/javascripts/repository/queries/getFiles.query.graphql2
-rw-r--r--app/assets/javascripts/repository/queries/getPermissions.query.graphql9
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue32
-rw-r--r--app/assets/javascripts/visual_review_toolbar/styles/toolbar.css1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue3
-rw-r--r--app/assets/javascripts/vue_shared/directives/tooltip.js4
-rw-r--r--app/assets/stylesheets/_ee/application_ee.scss5
-rw-r--r--app/assets/stylesheets/application.scss4
-rw-r--r--app/assets/stylesheets/components/avatar.scss5
-rw-r--r--app/assets/stylesheets/components/popover.scss1
-rw-r--r--app/assets/stylesheets/components/toast.scss13
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/asciidoctor.scss27
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss7
-rw-r--r--app/assets/stylesheets/framework/filters.scss53
-rw-r--r--app/assets/stylesheets/framework/icons.scss3
-rw-r--r--app/assets/stylesheets/framework/lists.scss1
-rw-r--r--app/assets/stylesheets/framework/modal.scss51
-rw-r--r--app/assets/stylesheets/framework/panels.scss1
-rw-r--r--app/assets/stylesheets/framework/typography.scss65
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/pages/commits.scss2
-rw-r--r--app/assets/stylesheets/pages/container_registry.scss4
-rw-r--r--app/assets/stylesheets/pages/diff.scss2
-rw-r--r--app/assets/stylesheets/pages/groups.scss4
-rw-r--r--app/assets/stylesheets/pages/help.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss20
-rw-r--r--app/assets/stylesheets/pages/labels.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss27
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss9
-rw-r--r--app/assets/stylesheets/pages/prometheus.scss5
-rw-r--r--app/assets/stylesheets/pages/reports.scss9
-rw-r--r--app/assets/stylesheets/pages/wiki.scss4
-rw-r--r--app/controllers/admin/application_settings_controller.rb12
-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.rb12
-rw-r--r--app/controllers/boards/issues_controller.rb32
-rw-r--r--app/controllers/chaos_controller.rb89
-rw-r--r--app/controllers/concerns/issuable_actions.rb4
-rw-r--r--app/controllers/concerns/requires_whitelisted_monitoring_client.rb2
-rw-r--r--app/controllers/graphql_controller.rb5
-rw-r--r--app/controllers/groups_controller.rb7
-rw-r--r--app/controllers/ide_controller.rb1
-rw-r--r--app/controllers/projects/cycle_analytics/events_controller.rb10
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb17
-rw-r--r--app/controllers/projects/issues_controller.rb8
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb8
-rw-r--r--app/controllers/projects/snippets_controller.rb3
-rw-r--r--app/controllers/projects/wikis_controller.rb5
-rw-r--r--app/controllers/projects_controller.rb8
-rw-r--r--app/controllers/registrations_controller.rb3
-rw-r--r--app/controllers/sessions_controller.rb7
-rw-r--r--app/controllers/snippets_controller.rb3
-rw-r--r--app/finders/autocomplete/move_to_project_finder.rb9
-rw-r--r--app/graphql/gitlab_schema.rb2
-rw-r--r--app/graphql/mutations/award_emojis/base.rb2
-rw-r--r--app/graphql/mutations/base_mutation.rb1
-rw-r--r--app/graphql/mutations/merge_requests/base.rb1
-rw-r--r--app/graphql/mutations/notes/base.rb32
-rw-r--r--app/graphql/mutations/notes/create/base.rb49
-rw-r--r--app/graphql/mutations/notes/create/diff_note.rb33
-rw-r--r--app/graphql/mutations/notes/create/image_diff_note.rb33
-rw-r--r--app/graphql/mutations/notes/create/note.rb40
-rw-r--r--app/graphql/mutations/notes/destroy.rb28
-rw-r--r--app/graphql/mutations/notes/update.rb38
-rw-r--r--app/graphql/types/base_input_object.rb1
-rw-r--r--app/graphql/types/diff_paths_input_type.rb12
-rw-r--r--app/graphql/types/diff_refs_type.rb14
-rw-r--r--app/graphql/types/merge_request_type.rb1
-rw-r--r--app/graphql/types/mutation_type.rb5
-rw-r--r--app/graphql/types/notes/diff_image_position_input_type.rb20
-rw-r--r--app/graphql/types/notes/diff_position_base_input_type.rb22
-rw-r--r--app/graphql/types/notes/diff_position_input_type.rb16
-rw-r--r--app/graphql/types/notes/diff_position_type.rb7
-rw-r--r--app/graphql/types/notes/discussion_type.rb8
-rw-r--r--app/graphql/types/tree/submodule_type.rb3
-rw-r--r--app/graphql/types/tree/tree_type.rb4
-rw-r--r--app/helpers/blob_helper.rb11
-rw-r--r--app/helpers/boards_helper.rb6
-rw-r--r--app/helpers/dashboard_helper.rb15
-rw-r--r--app/helpers/icons_helper.rb21
-rw-r--r--app/helpers/issuables_helper.rb4
-rw-r--r--app/helpers/markup_helper.rb12
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/sorting_helper.rb83
-rw-r--r--app/helpers/storage_helper.rb2
-rw-r--r--app/helpers/submodule_helper.rb22
-rw-r--r--app/helpers/tree_helper.rb39
-rw-r--r--app/helpers/visibility_level_helper.rb22
-rw-r--r--app/mailers/notify.rb12
-rw-r--r--app/models/active_session.rb20
-rw-r--r--app/models/chat_team.rb2
-rw-r--r--app/models/ci/build.rb12
-rw-r--r--app/models/ci/job_artifact.rb4
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/ci/pipeline_schedule.rb3
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/ci/trigger.rb11
-rw-r--r--app/models/clusters/applications/runner.rb17
-rw-r--r--app/models/clusters/concerns/application_core.rb10
-rw-r--r--app/models/clusters/concerns/application_status.rb24
-rw-r--r--app/models/concerns/cache_markdown_field.rb15
-rw-r--r--app/models/concerns/cacheable_attributes.rb2
-rw-r--r--app/models/concerns/case_sensitivity.rb4
-rw-r--r--app/models/concerns/ci/contextable.rb1
-rw-r--r--app/models/concerns/deployment_platform.rb23
-rw-r--r--app/models/concerns/from_union.rb6
-rw-r--r--app/models/concerns/has_status.rb66
-rw-r--r--app/models/concerns/issuable.rb7
-rw-r--r--app/models/concerns/mentionable.rb3
-rw-r--r--app/models/concerns/project_api_compatibility.rb18
-rw-r--r--app/models/concerns/project_features_compatibility.rb54
-rw-r--r--app/models/concerns/relative_positioning.rb2
-rw-r--r--app/models/concerns/routable.rb43
-rw-r--r--app/models/concerns/stepable.rb35
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb2
-rw-r--r--app/models/concerns/taskable.rb3
-rw-r--r--app/models/concerns/token_authenticatable.rb2
-rw-r--r--app/models/cycle_analytics.rb46
-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.rb23
-rw-r--r--app/models/dashboard_group_milestone.rb3
-rw-r--r--app/models/deployment.rb11
-rw-r--r--app/models/discussion.rb11
-rw-r--r--app/models/email.rb3
-rw-r--r--app/models/environment.rb47
-rw-r--r--app/models/environment_status.rb6
-rw-r--r--app/models/group.rb10
-rw-r--r--app/models/issue.rb5
-rw-r--r--app/models/label_note.rb18
-rw-r--r--app/models/merge_request.rb11
-rw-r--r--app/models/merge_requests_closing_issues.rb2
-rw-r--r--app/models/namespace.rb11
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/pages_domain.rb12
-rw-r--r--app/models/personal_access_token.rb1
-rw-r--r--app/models/project.rb60
-rw-r--r--app/models/project_auto_devops.rb2
-rw-r--r--app/models/project_feature.rb20
-rw-r--r--app/models/project_import_state.rb2
-rw-r--r--app/models/project_services/ci_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb10
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/models/prometheus_metric.rb65
-rw-r--r--app/models/prometheus_metric_enums.rb70
-rw-r--r--app/models/remote_mirror.rb4
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/models/resource_label_event.rb7
-rw-r--r--app/models/ssh_host_key.rb2
-rw-r--r--app/models/storage/legacy_project.rb2
-rw-r--r--app/models/uploads/base.rb2
-rw-r--r--app/models/user.rb17
-rw-r--r--app/policies/ci/trigger_policy.rb2
-rw-r--r--app/policies/clusters/instance_policy.rb7
-rw-r--r--app/policies/concerns/clusterable_actions.rb14
-rw-r--r--app/policies/group_policy.rb12
-rw-r--r--app/policies/project_policy.rb6
-rw-r--r--app/presenters/blob_presenter.rb8
-rw-r--r--app/presenters/blobs/unfold_presenter.rb30
-rw-r--r--app/presenters/clusterable_presenter.rb14
-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/current_board_entity.rb6
-rw-r--r--app/serializers/current_board_serializer.rb5
-rw-r--r--app/serializers/diff_file_base_entity.rb11
-rw-r--r--app/serializers/diffs_entity.rb5
-rw-r--r--app/serializers/discussion_serializer.rb14
-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/submodule_entity.rb25
-rw-r--r--app/services/akismet_service.rb4
-rw-r--r--app/services/audit_event_service.rb6
-rw-r--r--app/services/boards/issues/move_service.rb63
-rw-r--r--app/services/ci/archive_trace_service.rb30
-rw-r--r--app/services/ci/process_pipeline_service.rb4
-rw-r--r--app/services/ci/register_job_service.rb2
-rw-r--r--app/services/clusters/applications/check_uninstall_progress_service.rb1
-rw-r--r--app/services/clusters/create_service.rb17
-rw-r--r--app/services/clusters/gcp/kubernetes.rb2
-rw-r--r--app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb41
-rw-r--r--app/services/commits/create_service.rb18
-rw-r--r--app/services/concerns/exclusive_lease_guard.rb2
-rw-r--r--app/services/files/multi_service.rb1
-rw-r--r--app/services/groups/create_service.rb3
-rw-r--r--app/services/groups/destroy_service.rb2
-rw-r--r--app/services/issuable/bulk_update_service.rb10
-rw-r--r--app/services/issuable/clone/attributes_rewriter.rb2
-rw-r--r--app/services/issuable/clone/content_rewriter.rb6
-rw-r--r--app/services/issuable_base_service.rb4
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/labels/create_service.rb2
-rw-r--r--app/services/merge_requests/merge_base_service.rb11
-rw-r--r--app/services/merge_requests/merge_service.rb8
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb18
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb42
-rw-r--r--app/services/notification_service.rb4
-rw-r--r--app/services/projects/after_import_service.rb2
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/hashed_storage/migrate_attachments_service.rb2
-rw-r--r--app/services/projects/hashed_storage/rollback_attachments_service.rb2
-rw-r--r--app/services/projects/hashed_storage/rollback_service.rb2
-rw-r--r--app/services/projects/import_export/export_service.rb4
-rw-r--r--app/services/projects/propagate_service_template.rb2
-rw-r--r--app/services/projects/update_statistics_service.rb2
-rw-r--r--app/services/self_monitoring/project/create_service.rb132
-rw-r--r--app/services/submit_usage_ping_service.rb2
-rw-r--r--app/services/web_hook_service.rb2
-rw-r--r--app/services/wiki_pages/base_service.rb6
-rw-r--r--app/uploaders/file_mover.rb2
-rw-r--r--app/uploaders/records_uploads.rb10
-rw-r--r--app/validators/namespace_name_validator.rb12
-rw-r--r--app/validators/qualified_domain_array_validator.rb49
-rw-r--r--app/views/admin/application_settings/_pages.html.haml35
-rw-r--r--app/views/admin/dashboard/index.html.haml53
-rw-r--r--app/views/admin/groups/show.html.haml11
-rw-r--r--app/views/admin/projects/show.html.haml11
-rw-r--r--app/views/admin/requests_profiles/index.html.haml3
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/ci/status/_icon.html.haml13
-rw-r--r--app/views/dashboard/milestones/index.html.haml2
-rw-r--r--app/views/devise/passwords/edit.html.haml6
-rw-r--r--app/views/devise/sessions/_new_base.html.haml6
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml6
-rw-r--r--app/views/devise/shared/_signup_box.html.haml14
-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/authorizations/new.html.haml2
-rw-r--r--app/views/groups/_group_admin_settings.html.haml6
-rw-r--r--app/views/groups/issues.html.haml9
-rw-r--r--app/views/groups/merge_requests.html.haml8
-rw-r--r--app/views/groups/settings/_permissions.html.haml1
-rw-r--r--app/views/groups/settings/_subgroup_creation_level.html.haml3
-rw-r--r--app/views/help/index.html.haml2
-rw-r--r--app/views/layouts/_search.html.haml4
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml4
-rw-r--r--app/views/layouts/header/_default.html.haml4
-rw-r--r--app/views/layouts/header/_new_dropdown.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml19
-rw-r--r--app/views/layouts/nav/sidebar/_project_packages_link.html.haml16
-rw-r--r--app/views/notify/pages_domain_disabled_email.html.haml2
-rw-r--r--app/views/notify/pages_domain_disabled_email.text.haml2
-rw-r--r--app/views/notify/pages_domain_enabled_email.html.haml2
-rw-r--r--app/views/notify/pages_domain_enabled_email.text.haml2
-rw-r--r--app/views/notify/pages_domain_verification_failed_email.html.haml2
-rw-r--r--app/views/notify/pages_domain_verification_failed_email.text.haml2
-rw-r--r--app/views/notify/pages_domain_verification_succeeded_email.html.haml2
-rw-r--r--app/views/notify/pages_domain_verification_succeeded_email.text.haml2
-rw-r--r--app/views/projects/_files.html.haml3
-rw-r--r--app/views/projects/_new_project_fields.html.haml22
-rw-r--r--app/views/projects/environments/show.html.haml3
-rw-r--r--app/views/projects/issues/import_csv/_modal.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml10
-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/new.html.haml33
-rw-r--r--app/views/projects/pages_domains/_form.html.haml4
-rw-r--r--app/views/projects/pages_domains/_helper_text.html.haml8
-rw-r--r--app/views/projects/pages_domains/show.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_templates/_built_in_templates.html.haml4
-rw-r--r--app/views/projects/settings/operations/_external_dashboard.html.haml2
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-rw-r--r--app/views/projects/triggers/_content.html.haml17
-rw-r--r--app/views/projects/triggers/_trigger.html.haml7
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml4
-rw-r--r--app/views/shared/_storage_counter_statistics.html.haml4
-rw-r--r--app/views/shared/_visibility_radios.html.haml18
-rw-r--r--app/views/shared/boards/_show.html.haml3
-rw-r--r--app/views/shared/boards/_switcher.html.haml16
-rw-r--r--app/views/shared/issuable/_form.html.haml5
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml8
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml6
-rw-r--r--app/workers/all_queues.yml6
-rw-r--r--app/workers/archive_trace_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.rb12
-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/concerns/chaos_queue.rb9
-rw-r--r--app/workers/concerns/new_issuable.rb2
-rw-r--r--app/workers/create_gpg_signature_worker.rb2
-rw-r--r--app/workers/delete_user_worker.rb2
-rw-r--r--app/workers/email_receiver_worker.rb2
-rw-r--r--app/workers/expire_build_artifacts_worker.rb2
-rw-r--r--app/workers/expire_build_instance_artifacts_worker.rb2
-rw-r--r--app/workers/gitlab_usage_ping_worker.rb6
-rw-r--r--app/workers/new_note_worker.rb2
-rw-r--r--app/workers/object_storage/migrate_uploads_worker.rb4
-rw-r--r--app/workers/pages_domain_ssl_renewal_cron_worker.rb4
-rw-r--r--app/workers/pages_domain_ssl_renewal_worker.rb2
-rw-r--r--app/workers/repository_fork_worker.rb2
-rw-r--r--app/workers/repository_import_worker.rb2
-rw-r--r--app/workers/repository_update_remote_mirror_worker.rb2
-rw-r--r--app/workers/run_pipeline_schedule_worker.rb2
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb4
-rw-r--r--app/workers/stuck_import_jobs_worker.rb2
-rw-r--r--app/workers/stuck_merge_jobs_worker.rb2
-rw-r--r--app/workers/trending_projects_worker.rb2
-rw-r--r--app/workers/update_merge_requests_worker.rb2
-rw-r--r--app/workers/upload_checksum_worker.rb2
-rwxr-xr-xbin/bundle2
-rwxr-xr-xbin/setup31
-rwxr-xr-xbin/update6
-rwxr-xr-xbin/web68
-rwxr-xr-xbin/web_unicorn58
-rwxr-xr-xbin/yarn11
-rw-r--r--changelogs/README.md2
-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/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/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml5
-rw-r--r--changelogs/unreleased/21671-multiple-pipeline-status-api.yml5
-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.yml (renamed from changelogs/unreleased/-30974-issue-search-by-number.yml)2
-rw-r--r--changelogs/unreleased/32452-multiple-discussions.yml5
-rw-r--r--changelogs/unreleased/38105-pre-release-tag.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/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/49814-display-in-admin-area-if-emails-are-verified-or-not.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/52366-improved-group-lists-ui.yml5
-rw-r--r--changelogs/unreleased/52442-minimal-remove-mysql-support.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/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/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/58065-uniform-html-txt-email.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/60617-enable-project-cluster-jit.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/62124-new-threaded-discussion-design.yml5
-rw-r--r--changelogs/unreleased/62183-update-response-code-for-bulk-delete-api-for-container-registry.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/62938-wcag-aa-edited-text-color.yml5
-rw-r--r--changelogs/unreleased/62968-environment-details-header-border-misaligned.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/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/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/63559-remove-avatar-from-sign-in.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/63833-fix-jira-issues-url.yml5
-rw-r--r--changelogs/unreleased/63873-process-start-time.yml6
-rw-r--r--changelogs/unreleased/63971-remove-istanbul.yml5
-rw-r--r--changelogs/unreleased/64081-override-helm-release-name.yml5
-rw-r--r--changelogs/unreleased/64091-fix-sprockets-paths.yml5
-rw-r--r--changelogs/unreleased/64160-fix-duplicate-buttons.yml5
-rw-r--r--changelogs/unreleased/64176-fix-error-handling.yml5
-rw-r--r--changelogs/unreleased/64257-active_session_lookup_key_cleanup.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/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/64731-fix-project-auto-devops-api.yml5
-rw-r--r--changelogs/unreleased/64870-can-t-save-pages-domain-form-with-let-s-encrypt-enabled-if-current-certificate-is-outdated.yml6
-rw-r--r--changelogs/unreleased/64974-remove-livesum-from-ruby-sampler-metrics.yml5
-rw-r--r--changelogs/unreleased/65019-auto-devops-dind-tls-fix.yml5
-rw-r--r--changelogs/unreleased/65019-job-templates-dind-tls-fix.yml5
-rw-r--r--changelogs/unreleased/FixLocaleEN.yml5
-rw-r--r--changelogs/unreleased/GL-12412.yml5
-rw-r--r--changelogs/unreleased/Remove-unresolved-class-in-discussion-header.yml5
-rw-r--r--changelogs/unreleased/add-clusters-to-deployment.yml5
-rw-r--r--changelogs/unreleased/add-metrics-dashboard-permission-check.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/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-sidekiq-chaos.yml5
-rw-r--r--changelogs/unreleased/asciidoc-enable-syntax-highlighting.yml5
-rw-r--r--changelogs/unreleased/asciidoctor-upgrade.yml5
-rw-r--r--changelogs/unreleased/bjk-64064_cache_metrics.yml5
-rw-r--r--changelogs/unreleased/bjk-usage_ping.yml5
-rw-r--r--changelogs/unreleased/bvl-mark-remote-mirrors-as-failed-sooner.yml5
-rw-r--r--changelogs/unreleased/bvl-markdown-graphql.yml5
-rw-r--r--changelogs/unreleased/bw-add-index-for-relative-position.yml5
-rw-r--r--changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.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/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/dm-submodule-helper-routing.yml5
-rw-r--r--changelogs/unreleased/dohtaset.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/expose-saml-provider-id-to-users-api.yml5
-rw-r--r--changelogs/unreleased/fe-delete-old-boardservice.yml6
-rw-r--r--changelogs/unreleased/fe-issue-reorder.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/fix-alignment-on-security-reports.yml5
-rw-r--r--changelogs/unreleased/fix-facivon-url-if-uploads-object-store-enabled.yml5
-rw-r--r--changelogs/unreleased/fix-i18n-updated-projects.yml5
-rw-r--r--changelogs/unreleased/fix-jupyter-git-v3.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/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/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.49.0.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.51.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/id-extract-widget-into-different-request.yml5
-rw-r--r--changelogs/unreleased/id-stale-branches.yml5
-rw-r--r--changelogs/unreleased/issue-63222.yml5
-rw-r--r--changelogs/unreleased/jc-detect-nfs-for-rugged.yml5
-rw-r--r--changelogs/unreleased/jivanvl-add-chart-empty-state.yml5
-rw-r--r--changelogs/unreleased/jprovazn-project-search.yml5
-rw-r--r--changelogs/unreleased/knative-0-6.yml5
-rw-r--r--changelogs/unreleased/limit-amount-of-tests-returned.yml5
-rw-r--r--changelogs/unreleased/maintainers-can-create-subgroup.yml5
-rw-r--r--changelogs/unreleased/mh-board-tooltips.yml5
-rw-r--r--changelogs/unreleased/mh-collapsible-boards.yml5
-rw-r--r--changelogs/unreleased/mh-colon-autocomplete.yml5
-rw-r--r--changelogs/unreleased/move-all-configs-to-global.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/po-raw-changes-encoding.yml5
-rw-r--r--changelogs/unreleased/pre-releases-38105a.yml5
-rw-r--r--changelogs/unreleased/refactor-sentry.yml5
-rw-r--r--changelogs/unreleased/remove-kubernetes-service-deployment-platform.yml5
-rw-r--r--changelogs/unreleased/remove_group_and_instance_clusters_feature_flag.yml5
-rw-r--r--changelogs/unreleased/require-pipeline-when-enabling-only-allow-merge-if-pipeline-succeeds.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-59581-related-merge-requests-count.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-force-random-password-user-api.yml5
-rw-r--r--changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.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-avoid-loading-pipeline-status.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-enable-bootsnap.yml5
-rw-r--r--changelogs/unreleased/sh-enable-ref-name-caching-discussions.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-handle-nil-replication-lag.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-optimize-todos-controller.yml5
-rw-r--r--changelogs/unreleased/sh-remove-import-columns-from-projects.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-subnets-ip-rate-limiter.yml5
-rw-r--r--changelogs/unreleased/sh-update-mermaid.yml4
-rw-r--r--changelogs/unreleased/sh-upgrade-rouge-3-5-1.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/support-jsonb-default-value.yml5
-rw-r--r--changelogs/unreleased/tc-rake-orphan-artifacts.yml5
-rw-r--r--changelogs/unreleased/transaction-metrics.yml5
-rw-r--r--changelogs/unreleased/unicorn-sampler-fix.yml5
-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-pagination-texts.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/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-updateResolvableDiscussionsCounts-typo.yml5
-rw-r--r--changelogs/unreleased/zj-gitaly-usage-data.yml5
-rw-r--r--config/application.rb43
-rw-r--r--config/boot.rb6
-rw-r--r--config/brakeman.ignore24
-rw-r--r--config/database.yml.mysql60
-rw-r--r--config/database_geo.yml.postgresql51
-rw-r--r--config/environments/development.rb1
-rw-r--r--config/environments/test.rb3
-rw-r--r--config/gitlab.yml.example16
-rw-r--r--config/initializers/0_license.rb19
-rw-r--r--config/initializers/1_settings.rb4
-rw-r--r--config/initializers/7_prometheus_metrics.rb5
-rw-r--r--config/initializers/active_record_data_types.rb100
-rw-r--r--config/initializers/active_record_lifecycle.rb4
-rw-r--r--config/initializers/active_record_mysql_timestamp.rb30
-rw-r--r--config/initializers/active_record_preloader.rb17
-rw-r--r--config/initializers/active_record_query_cache.rb3
-rw-r--r--config/initializers/active_record_verbose_query_logs.rb56
-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/ar_speed_up_migration_checking.rb16
-rw-r--r--config/initializers/config_initializers_active_record_locking.rb9
-rw-r--r--config/initializers/connection_fix.rb32
-rw-r--r--config/initializers/console_message.rb13
-rw-r--r--config/initializers/deprecations.rb2
-rw-r--r--config/initializers/elastic_client_setup.rb50
-rw-r--r--config/initializers/geo.rb17
-rw-r--r--config/initializers/health_check.rb6
-rw-r--r--config/initializers/httpclient_patch.rb18
-rw-r--r--config/initializers/load_balancing.rb25
-rw-r--r--config/initializers/lograge.rb9
-rw-r--r--config/initializers/mysql_ignore_postgresql_options.rb49
-rw-r--r--config/initializers/mysql_set_length_for_binary_indexes.rb30
-rw-r--r--config/initializers/peek.rb9
-rw-r--r--config/initializers/postgresql_cte.rb4
-rw-r--r--config/initializers/postgresql_opclasses_support.rb211
-rw-r--r--config/initializers/rack_timeout.rb4
-rw-r--r--config/initializers/sidekiq.rb17
-rw-r--r--config/initializers/sidekiq_cluster.rb20
-rw-r--r--config/initializers/zz_metrics.rb33
-rw-r--r--config/karma.config.js10
-rw-r--r--config/locales/en.yml6
-rw-r--r--config/prometheus/cluster_metrics.yml63
-rw-r--r--config/pseudonymizer.yml475
-rw-r--r--config/routes.rb25
-rw-r--r--config/routes/admin.rb2
-rw-r--r--config/routes/api.rb8
-rw-r--r--config/settings.rb39
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--config/webpack.config.js6
-rw-r--r--danger/commit_messages/Dangerfile23
-rw-r--r--danger/database/Dangerfile42
-rw-r--r--danger/gemfile/Dangerfile3
-rw-r--r--danger/metadata/Dangerfile12
-rw-r--r--danger/roulette/Dangerfile15
-rw-r--r--db/fixtures/development/14_pipelines.rb18
-rw-r--r--db/fixtures/development/99_common_metrics.rb4
-rw-r--r--db/fixtures/production/999_common_metrics.rb4
-rw-r--r--db/importers/common_metrics_importer.rb105
-rw-r--r--db/migrate/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/20180831164904_fix_prometheus_metric_query_limits.rb19
-rw-r--r--db/migrate/20180831164910_import_common_metrics.rb4
-rw-r--r--db/migrate/20181006004100_import_common_metrics_nginx_vts.rb4
-rw-r--r--db/migrate/20181030154446_add_missing_indexes_for_foreign_keys.rb4
-rw-r--r--db/migrate/20190206193120_add_index_to_tags.rb2
-rw-r--r--db/migrate/20190326164045_import_common_metrics_knative.rb4
-rw-r--r--db/migrate/20190408163745_prometheus_knative05_fix.rb4
-rw-r--r--db/migrate/20190620105427_change_null_private_profile_to_false.rb33
-rw-r--r--db/migrate/20190626175626_add_group_creation_level_to_namespaces.rb18
-rw-r--r--db/migrate/20190709204413_add_rule_type_to_approval_project_rules.rb17
-rw-r--r--db/migrate/20190709220143_add_index_to_issues_relative_position.rb20
-rw-r--r--db/migrate/20190710151229_add_index_to_approval_project_rules_rule_type.rb17
-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/20180223124427_build_user_interacted_projects_table.rb4
-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/20190628191740_schedule_fixing_names_of_user_namespaces.rb48
-rw-r--r--db/post_migrate/20190702173936_populate_remaining_merge_request_assignees.rb20
-rw-r--r--db/post_migrate/20190703185326_fix_wrong_pages_access_level.rb28
-rw-r--r--db/post_migrate/20190715114644_drop_project_features_pages_access_level_default.rb12
-rw-r--r--db/schema.rb1424
-rw-r--r--doc/README.md4
-rw-r--r--doc/administration/audit_events.md3
-rw-r--r--doc/administration/auditor_users.md2
-rw-r--r--doc/administration/auth/README.md37
-rw-r--r--doc/administration/auth/authentiq.md85
-rw-r--r--doc/administration/auth/crowd.md77
-rw-r--r--doc/administration/auth/google_secure_ldap.md22
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md20
-rw-r--r--doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md22
-rw-r--r--doc/administration/auth/jwt.md134
-rw-r--r--doc/administration/auth/ldap-ee.md262
-rw-r--r--doc/administration/auth/ldap.md92
-rw-r--r--doc/administration/auth/oidc.md159
-rw-r--r--doc/administration/auth/okta.md232
-rw-r--r--doc/administration/auth/smartcard.md86
-rw-r--r--doc/administration/container_registry.md7
-rw-r--r--doc/administration/dependency_proxy.md1
-rw-r--r--doc/administration/environment_variables.md1
-rw-r--r--doc/administration/geo/disaster_recovery/background_verification.md17
-rw-r--r--doc/administration/geo/disaster_recovery/bring_primary_back.md2
-rw-r--r--doc/administration/geo/disaster_recovery/planned_failover.md2
-rw-r--r--doc/administration/geo/replication/database.md34
-rw-r--r--doc/administration/geo/replication/external_database.md1
-rw-r--r--doc/administration/geo/replication/remove_geo_node.md12
-rw-r--r--doc/administration/geo/replication/updating_the_geo_nodes.md10
-rw-r--r--doc/administration/gitaly/index.md670
-rw-r--r--doc/administration/high_availability/README.md13
-rw-r--r--doc/administration/high_availability/alpha_database.md3
-rw-r--r--doc/administration/high_availability/consul.md72
-rw-r--r--doc/administration/high_availability/database.md492
-rw-r--r--doc/administration/high_availability/gitaly.md16
-rw-r--r--doc/administration/high_availability/gitlab.md136
-rw-r--r--doc/administration/high_availability/load_balancer.md16
-rw-r--r--doc/administration/high_availability/monitoring_node.md96
-rw-r--r--doc/administration/high_availability/nfs.md50
-rw-r--r--doc/administration/high_availability/nfs_host_client_setup.md16
-rw-r--r--doc/administration/high_availability/pgbouncer.md168
-rw-r--r--doc/administration/high_availability/redis.md409
-rw-r--r--doc/administration/high_availability/redis_source.md311
-rw-r--r--doc/administration/housekeeping.md3
-rw-r--r--doc/administration/img/audit_log.png (renamed from doc/administration/audit_log.png)bin25767 -> 25767 bytes
-rw-r--r--doc/administration/img/auditor_access_form.png (renamed from doc/administration/auditor_access_form.png)bin11910 -> 11910 bytes
-rw-r--r--doc/administration/integration/plantuml.md71
-rw-r--r--doc/administration/integration/terminal.md4
-rw-r--r--doc/administration/job_artifacts.md19
-rw-r--r--doc/administration/job_traces.md6
-rw-r--r--doc/administration/logs.md17
-rw-r--r--doc/administration/merge_request_diffs.md1
-rw-r--r--doc/administration/monitoring/ip_whitelist.md20
-rw-r--r--doc/administration/monitoring/performance/gitlab_configuration.md6
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar.pngbin99331 -> 127198 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.pngbin91275 -> 203373 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_redis_calls.pngbin0 -> 191734 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_rugged_calls.pngbin0 -> 274852 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_sql_queries.pngbin128337 -> 338952 bytes
-rw-r--r--doc/administration/monitoring/performance/img/request_profile_result.pngbin3216 -> 33920 bytes
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md12
-rw-r--r--doc/administration/monitoring/performance/request_profiling.md4
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md3
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md6
-rw-r--r--doc/administration/monitoring/prometheus/index.md142
-rw-r--r--doc/administration/monitoring/prometheus/node_exporter.md6
-rw-r--r--doc/administration/monitoring/prometheus/pgbouncer_exporter.md6
-rw-r--r--doc/administration/monitoring/prometheus/postgres_exporter.md6
-rw-r--r--doc/administration/monitoring/prometheus/redis_exporter.md6
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md104
-rw-r--r--doc/administration/operations/filesystem_benchmarking.md33
-rw-r--r--doc/administration/pages/img/lets_encrypt_integration_v12_1.pngbin0 -> 98409 bytes
-rw-r--r--doc/administration/pages/index.md195
-rw-r--r--doc/administration/pages/source.md248
-rw-r--r--doc/administration/pseudonymizer.md4
-rw-r--r--doc/administration/raketasks/geo.md4
-rw-r--r--doc/administration/raketasks/maintenance.md19
-rw-r--r--doc/administration/raketasks/project_import_export.md16
-rw-r--r--doc/administration/raketasks/storage.md29
-rw-r--r--doc/administration/raketasks/uploads/sanitize.md16
-rw-r--r--doc/administration/repository_checks.md4
-rw-r--r--doc/administration/repository_storage_paths.md4
-rw-r--r--doc/administration/repository_storage_types.md23
-rw-r--r--doc/administration/troubleshooting/debug.md108
-rw-r--r--doc/administration/uploads.md105
-rw-r--r--doc/api/README.md18
-rw-r--r--doc/api/commits.md11
-rw-r--r--doc/api/container_registry.md4
-rw-r--r--doc/api/dependencies.md50
-rw-r--r--doc/api/discussions.md230
-rw-r--r--doc/api/graphql/index.md26
-rw-r--r--doc/api/graphql/reference/index.md507
-rw-r--r--doc/api/group_clusters.md280
-rw-r--r--doc/api/groups.md2
-rw-r--r--doc/api/issues.md1
-rw-r--r--doc/api/jobs.md14
-rw-r--r--doc/api/lint.md2
-rw-r--r--doc/api/merge_requests.md8
-rw-r--r--doc/api/projects.md127
-rw-r--r--doc/api/protected_tags.md1
-rw-r--r--doc/api/releases/img/upcoming_release_v12_1.pngbin0 -> 22635 bytes
-rw-r--r--doc/api/releases/index.md18
-rw-r--r--doc/api/repositories.md24
-rw-r--r--doc/api/repository_submodules.md2
-rw-r--r--doc/api/system_hooks.md8
-rw-r--r--doc/api/tags.md1
-rw-r--r--doc/api/users.md59
-rw-r--r--doc/api/vulnerabilities.md2
-rw-r--r--doc/ci/caching/index.md2
-rw-r--r--doc/ci/docker/using_docker_build.md28
-rw-r--r--doc/ci/docker/using_docker_images.md69
-rw-r--r--doc/ci/examples/README.md1
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md4
-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.md1
-rw-r--r--doc/ci/examples/php.md2
-rw-r--r--doc/ci/git_submodules.md14
-rw-r--r--doc/ci/interactive_web_terminal/index.md3
-rw-r--r--doc/ci/introduction/index.md36
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md9
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.pngbin0 -> 18121 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md39
-rw-r--r--doc/ci/services/mysql.md6
-rw-r--r--doc/ci/services/postgres.md9
-rw-r--r--doc/ci/variables/README.md5
-rw-r--r--doc/ci/variables/predefined_variables.md1
-rw-r--r--doc/ci/yaml/README.md80
-rw-r--r--doc/development/README.md4
-rw-r--r--doc/development/api_graphql_styleguide.md8
-rw-r--r--doc/development/architecture.md2
-rw-r--r--doc/development/changelog.md1
-rw-r--r--doc/development/chaos_endpoints.md82
-rw-r--r--doc/development/code_review.md42
-rw-r--r--doc/development/contributing/issue_workflow.md75
-rw-r--r--doc/development/contributing/merge_request_workflow.md11
-rw-r--r--doc/development/contributing/style_guides.md1
-rw-r--r--doc/development/database_debugging.md34
-rw-r--r--doc/development/database_review.md101
-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.md60
-rw-r--r--doc/development/documentation/site_architecture/global_nav.md12
-rw-r--r--doc/development/documentation/site_architecture/index.md8
-rw-r--r--doc/development/documentation/structure.md4
-rw-r--r--doc/development/documentation/styleguide.md32
-rw-r--r--doc/development/documentation/workflow.md2
-rw-r--r--doc/development/fe_guide/design_patterns.md3
-rw-r--r--doc/development/fe_guide/droplab/droplab.md8
-rw-r--r--doc/development/fe_guide/droplab/plugins/ajax.md19
-rw-r--r--doc/development/fe_guide/droplab/plugins/filter.md37
-rw-r--r--doc/development/fe_guide/droplab/plugins/input_setter.md53
-rw-r--r--doc/development/fe_guide/event_tracking.md2
-rw-r--r--doc/development/fe_guide/frontend_faq.md6
-rw-r--r--doc/development/fe_guide/icons.md16
-rw-r--r--doc/development/fe_guide/index.md10
-rw-r--r--doc/development/fe_guide/style_guide_js.md4
-rw-r--r--doc/development/fe_guide/style_guide_scss.md2
-rw-r--r--doc/development/fe_guide/vue.md1
-rw-r--r--doc/development/file_storage.md4
-rw-r--r--doc/development/geo.md26
-rw-r--r--doc/development/git_object_deduplication.md110
-rw-r--r--doc/development/gitaly.md19
-rw-r--r--doc/development/go_guide/index.md37
-rw-r--r--doc/development/gotchas.md2
-rw-r--r--doc/development/i18n/proofreader.md29
-rw-r--r--doc/development/import_export.md5
-rw-r--r--doc/development/integrations/jira_connect.md2
-rw-r--r--doc/development/interacting_components.md29
-rw-r--r--doc/development/licensed_feature_availability.md6
-rw-r--r--doc/development/new_fe_guide/index.md1
-rw-r--r--doc/development/new_fe_guide/style/javascript.md5
-rw-r--r--doc/development/new_fe_guide/style/prettier.md3
-rw-r--r--doc/development/performance.md9
-rw-r--r--doc/development/policies.md4
-rw-r--r--doc/development/prometheus_metrics.md4
-rw-r--r--doc/development/shell_scripting_guide/index.md117
-rw-r--r--doc/development/testing_guide/end_to_end/index.md2
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md40
-rw-r--r--doc/development/testing_guide/end_to_end/quick_start_guide.md59
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md13
-rw-r--r--doc/development/testing_guide/frontend_testing.md55
-rw-r--r--doc/development/testing_guide/index.md18
-rw-r--r--doc/development/testing_guide/review_apps.md8
-rw-r--r--doc/gitlab-basics/command-line-commands.md1
-rw-r--r--doc/gitlab-basics/create-project.md32
-rw-r--r--doc/install/aws/index.md141
-rw-r--r--doc/install/azure/index.md31
-rw-r--r--doc/install/digitaloceandocker.md32
-rw-r--r--doc/install/google_cloud_platform/index.md38
-rw-r--r--doc/install/installation.md89
-rw-r--r--doc/install/openshift_and_gitlab/index.md100
-rw-r--r--doc/install/relative_url.md82
-rw-r--r--doc/integration/auth0.md88
-rw-r--r--doc/integration/bitbucket.md182
-rw-r--r--doc/integration/cas.md84
-rw-r--r--doc/integration/elasticsearch.md124
-rw-r--r--doc/integration/facebook.md66
-rw-r--r--doc/integration/github.md180
-rw-r--r--doc/integration/gitlab.md96
-rw-r--r--doc/integration/google.md106
-rw-r--r--doc/integration/jenkins_deprecated.md2
-rw-r--r--doc/integration/jira_development_panel.md6
-rw-r--r--doc/integration/kerberos.md104
-rw-r--r--doc/integration/oauth2_generic.md26
-rw-r--r--doc/integration/oauth_provider.md12
-rw-r--r--doc/integration/omniauth.md122
-rw-r--r--doc/integration/salesforce.md98
-rw-r--r--doc/integration/shibboleth.md182
-rw-r--r--doc/integration/ultra_auth.md135
-rw-r--r--doc/migrate_ci_to_ce/README.md14
-rw-r--r--doc/pages/getting_started_part_three.md4
-rw-r--r--doc/raketasks/backup_restore.md16
-rw-r--r--doc/raketasks/cleanup.md10
-rw-r--r--doc/raketasks/import.md2
-rw-r--r--doc/security/information_exclusivity.md1
-rw-r--r--doc/security/password_length_limits.md30
-rw-r--r--doc/security/rack_attack.md77
-rw-r--r--doc/security/reset_root_password.md3
-rw-r--r--doc/security/ssh_keys_restrictions.md1
-rw-r--r--doc/security/two_factor_authentication.md7
-rw-r--r--doc/security/unlock_user.md47
-rw-r--r--doc/security/user_email_confirmation.md1
-rw-r--r--doc/security/user_file_uploads.md3
-rw-r--r--doc/security/webhooks.md1
-rw-r--r--doc/ssh/README.md81
-rw-r--r--doc/subscriptions/index.md8
-rw-r--r--doc/system_hooks/system_hooks.md1
-rw-r--r--doc/topics/autodevops/index.md29
-rw-r--r--doc/university/training/topics/env_setup.md2
-rw-r--r--doc/university/training/topics/getting_started.md19
-rw-r--r--doc/user/admin_area/geo_nodes.md4
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md2
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md22
-rw-r--r--doc/user/admin_area/settings/img/admin_required_pipeline.pngbin0 -> 64548 bytes
-rw-r--r--doc/user/admin_area/settings/img/email_confirmation.pngbin0 -> 14260 bytes
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md33
-rw-r--r--doc/user/application_security/container_scanning/index.md133
-rw-r--r--doc/user/application_security/dast/index.md147
-rw-r--r--doc/user/application_security/dependency_scanning/index.md121
-rw-r--r--doc/user/application_security/index.md6
-rw-r--r--doc/user/application_security/license_management/index.md120
-rw-r--r--doc/user/application_security/sast/analyzers.md143
-rw-r--r--doc/user/application_security/sast/index.md146
-rw-r--r--doc/user/application_security/security_dashboard/index.md4
-rw-r--r--doc/user/asciidoc.md9
-rw-r--r--doc/user/award_emojis.md2
-rw-r--r--doc/user/clusters/applications.md13
-rw-r--r--doc/user/discussions/img/automatically_resolve_outdated_discussions.pngbin38001 -> 57642 bytes
-rw-r--r--doc/user/discussions/img/btn_new_issue_for_all_discussions.pngbin9555 -> 0 bytes
-rw-r--r--doc/user/discussions/img/btn_new_issue_for_all_threads.pngbin0 -> 19431 bytes
-rw-r--r--doc/user/discussions/img/commit_comment_mr_context.pngbin25854 -> 365956 bytes
-rw-r--r--doc/user/discussions/img/commit_comment_mr_discussions_tab.pngbin15139 -> 276385 bytes
-rw-r--r--doc/user/discussions/img/discussion_comment.pngbin37351 -> 60561 bytes
-rw-r--r--doc/user/discussions/img/discussion_view.pngbin73807 -> 0 bytes
-rw-r--r--doc/user/discussions/img/image_resolved_discussion.pngbin48234 -> 199539 bytes
-rw-r--r--doc/user/discussions/img/merge_request_commits_tab.pngbin12792 -> 169010 bytes
-rw-r--r--doc/user/discussions/img/mr_review_resolve.pngbin21941 -> 247868 bytes
-rw-r--r--doc/user/discussions/img/mr_review_resolve2.pngbin9430 -> 232589 bytes
-rw-r--r--doc/user/discussions/img/mr_review_second_comment.pngbin11188 -> 183733 bytes
-rw-r--r--doc/user/discussions/img/mr_review_second_comment_added.pngbin9673 -> 161924 bytes
-rw-r--r--doc/user/discussions/img/mr_review_start.pngbin23491 -> 274186 bytes
-rw-r--r--doc/user/discussions/img/mr_review_unresolve.pngbin11302 -> 298513 bytes
-rw-r--r--doc/user/discussions/img/new_issue_for_thread.pngbin0 -> 21119 bytes
-rw-r--r--doc/user/discussions/img/onion_skin_view.pngbin45053 -> 95809 bytes
-rw-r--r--doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.pngbin0 -> 53804 bytes
-rw-r--r--doc/user/discussions/img/pending_review_comment.pngbin8793 -> 247865 bytes
-rw-r--r--doc/user/discussions/img/preview_issue_for_thread.pngbin0 -> 119608 bytes
-rw-r--r--doc/user/discussions/img/preview_issue_for_threads.pngbin0 -> 131506 bytes
-rw-r--r--doc/user/discussions/img/resolve_comment_button.pngbin4713 -> 11680 bytes
-rw-r--r--doc/user/discussions/img/resolve_thread_button.pngbin0 -> 34562 bytes
-rw-r--r--doc/user/discussions/img/resolve_thread_issue_notice.pngbin0 -> 44429 bytes
-rw-r--r--doc/user/discussions/img/resolve_thread_open_issue.pngbin0 -> 57089 bytes
-rw-r--r--doc/user/discussions/img/review_comment_quickactions.pngbin12392 -> 270228 bytes
-rw-r--r--doc/user/discussions/img/review_preview.pngbin19769 -> 84789 bytes
-rw-r--r--doc/user/discussions/img/swipe_view.pngbin16483 -> 71076 bytes
-rw-r--r--doc/user/discussions/img/thread_view.pngbin0 -> 556169 bytes
-rw-r--r--doc/user/discussions/img/threads_resolved.pngbin0 -> 12667 bytes
-rw-r--r--doc/user/discussions/img/two_up_view.pngbin61759 -> 118688 bytes
-rw-r--r--doc/user/discussions/index.md218
-rw-r--r--doc/user/group/bulk_editing/img/bulk-editing.pngbin0 -> 100007 bytes
-rw-r--r--doc/user/group/bulk_editing/index.md26
-rw-r--r--doc/user/group/clusters/index.md8
-rw-r--r--doc/user/group/custom_project_templates.md2
-rw-r--r--doc/user/group/epics/index.md4
-rw-r--r--doc/user/group/index.md4
-rw-r--r--doc/user/group/saml_sso/img/group_saml_settings.pngbin89399 -> 140408 bytes
-rw-r--r--doc/user/group/saml_sso/index.md2
-rw-r--r--doc/user/group/saml_sso/scim_setup.md46
-rw-r--r--doc/user/group/subgroups/index.md64
-rw-r--r--doc/user/index.md6
-rw-r--r--doc/user/instance/clusters/index.md1
-rw-r--r--doc/user/markdown.md26
-rw-r--r--doc/user/permissions.md9
-rw-r--r--doc/user/profile/account/two_factor_authentication.md46
-rw-r--r--doc/user/project/autocomplete_characters.md48
-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.md174
-rw-r--r--doc/user/project/clusters/index.md235
-rw-r--r--doc/user/project/clusters/runbooks/index.md61
-rw-r--r--doc/user/project/clusters/serverless/index.md722
-rw-r--r--doc/user/project/code_owners.md4
-rw-r--r--doc/user/project/container_registry.md8
-rw-r--r--doc/user/project/cycle_analytics.md14
-rw-r--r--doc/user/project/deploy_boards.md14
-rw-r--r--doc/user/project/deploy_tokens/index.md6
-rw-r--r--doc/user/project/img/autocomplete_characters_example1_v12_0.pngbin0 -> 17510 bytes
-rw-r--r--doc/user/project/img/autocomplete_characters_example2_v12_0.pngbin0 -> 14623 bytes
-rw-r--r--doc/user/project/import/bitbucket_server.md5
-rw-r--r--doc/user/project/import/index.md4
-rw-r--r--doc/user/project/import/phabricator.md6
-rw-r--r--doc/user/project/import/svn.md10
-rw-r--r--doc/user/project/index.md23
-rw-r--r--doc/user/project/integrations/custom_issue_tracker.md4
-rw-r--r--doc/user/project/integrations/gitlab_slack_application.md2
-rw-r--r--doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.pngbin0 -> 65101 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.pngbin0 -> 26781 bytes
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md26
-rw-r--r--doc/user/project/integrations/mock_ci.md4
-rw-r--r--doc/user/project/integrations/project_services.md8
-rw-r--r--doc/user/project/integrations/prometheus.md157
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md24
-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/crosslinking_issues.md2
-rw-r--r--doc/user/project/issues/csv_export.md2
-rw-r--r--doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.pngbin0 -> 121644 bytes
-rw-r--r--doc/user/project/issues/img/confidential_mr_dropdown_v12_1.pngbin0 -> 128156 bytes
-rw-r--r--doc/user/project/issues/index.md5
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md26
-rw-r--r--doc/user/project/issues/managing_issues.md4
-rw-r--r--doc/user/project/issues/sorting_issue_lists.md30
-rw-r--r--doc/user/project/members/index.md22
-rw-r--r--doc/user/project/merge_requests/blocking_merge_requests.md133
-rw-r--r--doc/user/project/merge_requests/img/edit_blocking_merge_requests.pngbin0 -> 9926 bytes
-rw-r--r--doc/user/project/merge_requests/img/edit_blocking_merge_requests_inaccessible.pngbin0 -> 10867 bytes
-rw-r--r--doc/user/project/merge_requests/img/show_blocking_merge_requests_in_mr_widget.pngbin0 -> 27089 bytes
-rw-r--r--doc/user/project/merge_requests/index.md59
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md2
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md2
-rw-r--r--doc/user/project/merge_requests/work_in_progress_merge_requests.md6
-rw-r--r--doc/user/project/milestones/index.md4
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md4
-rw-r--r--doc/user/project/packages/maven_repository.md96
-rw-r--r--doc/user/project/packages/npm_registry.md38
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md95
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/add_certificate_to_pages.png (renamed from doc/user/project/pages/img/add_certificate_to_pages.png)bin14608 -> 14608 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/dns_add_new_a_record_example_updated_2018.png (renamed from doc/user/project/pages/img/dns_add_new_a_record_example_updated_2018.png)bin3701 -> 3701 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/dns_cname_record_example.png (renamed from doc/user/project/pages/img/dns_cname_record_example.png)bin4983 -> 4983 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.pngbin0 -> 194433 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.pngbin0 -> 35040 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.pngbin0 -> 191712 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/index.md277
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md87
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md75
-rw-r--r--doc/user/project/pages/getting_started_part_three.md315
-rw-r--r--doc/user/project/pages/getting_started_part_two.md4
-rw-r--r--doc/user/project/pages/img/pages_project_templates_v11_8.png (renamed from doc/user/project/pages/img/pages_project_templates_11-8.png)bin69702 -> 69702 bytes
-rw-r--r--doc/user/project/pages/img/verify_your_domain.pngbin12082 -> 0 bytes
-rw-r--r--doc/user/project/pages/index.md10
-rw-r--r--doc/user/project/pages/introduction.md26
-rw-r--r--doc/user/project/pages/lets_encrypt_for_gitlab_pages.md159
-rw-r--r--doc/user/project/pipelines/job_artifacts.md12
-rw-r--r--doc/user/project/pipelines/settings.md20
-rw-r--r--doc/user/project/quick_actions.md34
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md155
-rw-r--r--doc/user/project/repository/reducing_the_repo_size_using_git.md82
-rw-r--r--doc/user/project/repository/web_editor.md12
-rw-r--r--doc/user/project/service_desk.md45
-rw-r--r--doc/user/project/settings/import_export.md78
-rw-r--r--doc/user/project/settings/index.md4
-rw-r--r--doc/user/project/web_ide/index.md1
-rw-r--r--doc/user/search/advanced_search_syntax.md6
-rw-r--r--doc/user/search/index.md10
-rw-r--r--doc/workflow/README.md2
-rw-r--r--doc/workflow/file_finder.md4
-rw-r--r--doc/workflow/forking_workflow.md34
-rw-r--r--doc/workflow/issue_weight.md2
-rw-r--r--doc/workflow/lfs/lfs_administration.md61
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md2
-rw-r--r--doc/workflow/notifications.md2
-rw-r--r--doc/workflow/repository_mirroring.md12
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--doc/workflow/timezone.md8
-rw-r--r--doc/workflow/todos.md16
-rw-r--r--jest.config.js11
-rw-r--r--lib/api/api.rb5
-rw-r--r--lib/api/commit_statuses.rb4
-rw-r--r--lib/api/commits.rb16
-rw-r--r--lib/api/entities.rb44
-rw-r--r--lib/api/group_clusters.rb140
-rw-r--r--lib/api/helpers/graphql_helpers.rb2
-rw-r--r--lib/api/helpers/internal_helpers.rb2
-rw-r--r--lib/api/helpers/pagination.rb4
-rw-r--r--lib/api/helpers/projects_helpers.rb49
-rw-r--r--lib/api/helpers/runner.rb3
-rw-r--r--lib/api/import_github.rb2
-rw-r--r--lib/api/project_clusters.rb2
-rw-r--r--lib/api/projects.rb4
-rw-r--r--lib/api/releases.rb2
-rw-r--r--lib/api/user_counts.rb18
-rw-r--r--lib/api/users.rb10
-rw-r--r--lib/backup/database.rb27
-rw-r--r--lib/banzai/filter/ascii_doc_sanitization_filter.rb111
-rw-r--r--lib/banzai/filter/base_sanitization_filter.rb96
-rw-r--r--lib/banzai/filter/inline_embeds_filter.rb67
-rw-r--r--lib/banzai/filter/inline_metrics_filter.rb43
-rw-r--r--lib/banzai/filter/inline_metrics_redactor_filter.rb98
-rw-r--r--lib/banzai/filter/reference_redactor_filter.rb (renamed from lib/banzai/filter/redactor_filter.rb)4
-rw-r--r--lib/banzai/filter/sanitization_filter.rb82
-rw-r--r--lib/banzai/object_renderer.rb2
-rw-r--r--lib/banzai/pipeline/ascii_doc_pipeline.rb2
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb3
-rw-r--r--lib/banzai/reference_redactor.rb (renamed from lib/banzai/redactor.rb)2
-rw-r--r--lib/banzai/renderer.rb44
-rw-r--r--lib/feature/gitaly.rb6
-rw-r--r--lib/forever.rb10
-rw-r--r--lib/gitlab/access.rb11
-rw-r--r--lib/gitlab/asciidoc.rb1
-rw-r--r--lib/gitlab/auth.rb2
-rw-r--r--lib/gitlab/auth/ldap/adapter.rb4
-rw-r--r--lib/gitlab/auth/ldap/config.rb4
-rw-r--r--lib/gitlab/auth/ldap/person.rb6
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb17
-rw-r--r--lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb2
-rw-r--r--lib/gitlab/background_migration/archive_legacy_traces.rb2
-rw-r--r--lib/gitlab/background_migration/calculate_wiki_sizes.rb2
-rw-r--r--lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb19
-rw-r--r--lib/gitlab/background_migration/fix_cross_project_label_links.rb2
-rw-r--r--lib/gitlab/background_migration/fix_pages_access_level.rb128
-rw-r--r--lib/gitlab/background_migration/fix_user_namespace_names.rb68
-rw-r--r--lib/gitlab/background_migration/fix_user_project_route_names.rb38
-rw-r--r--lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb17
-rw-r--r--lib/gitlab/background_migration/populate_merge_request_assignees_table.rb8
-rw-r--r--lib/gitlab/background_migration/populate_untracked_uploads.rb2
-rw-r--r--lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb15
-rw-r--r--lib/gitlab/background_migration/prepare_untracked_uploads.rb7
-rw-r--r--lib/gitlab/batch_pop_queueing.rb112
-rw-r--r--lib/gitlab/chaos.rb49
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb6
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/abilities.rb4
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb11
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexer.rb17
-rw-r--r--lib/gitlab/ci/pipeline/expression/parser.rb39
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml2
-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.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/PHP.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Serverless.gitlab-ci.yml13
-rw-r--r--lib/gitlab/ci/trace.rb12
-rw-r--r--lib/gitlab/ci/trace/chunked_io.rb7
-rw-r--r--lib/gitlab/cleanup/orphan_job_artifact_files.rb2
-rw-r--r--lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb2
-rw-r--r--lib/gitlab/cleanup/project_upload_file_finder.rb2
-rw-r--r--lib/gitlab/cleanup/project_uploads.rb2
-rw-r--r--lib/gitlab/cleanup/remote_uploads.rb2
-rw-r--r--lib/gitlab/current_settings.rb2
-rw-r--r--lib/gitlab/cycle_analytics/base_event_fetcher.rb20
-rw-r--r--lib/gitlab/cycle_analytics/base_query.rb18
-rw-r--r--lib/gitlab/cycle_analytics/base_stage.rb54
-rw-r--r--lib/gitlab/cycle_analytics/code_event_fetcher.rb6
-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.rb6
-rw-r--r--lib/gitlab/cycle_analytics/issue_helper.rb5
-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.rb6
-rw-r--r--lib/gitlab/cycle_analytics/plan_helper.rb13
-rw-r--r--lib/gitlab/cycle_analytics/production_event_fetcher.rb6
-rw-r--r--lib/gitlab/cycle_analytics/production_helper.rb2
-rw-r--r--lib/gitlab/cycle_analytics/review_event_fetcher.rb6
-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/cycle_analytics/test_helper.rb2
-rw-r--r--lib/gitlab/cycle_analytics/usage_data.rb2
-rw-r--r--lib/gitlab/danger/helper.rb26
-rw-r--r--lib/gitlab/database.rb103
-rw-r--r--lib/gitlab/database/date_time.rb18
-rw-r--r--lib/gitlab/database/grant.rb2
-rw-r--r--lib/gitlab/database/median.rb47
-rw-r--r--lib/gitlab/database/migration_helpers.rb212
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb12
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb2
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb2
-rw-r--r--lib/gitlab/database_importers/common_metrics.rb8
-rw-r--r--lib/gitlab/database_importers/common_metrics/importer.rb78
-rw-r--r--lib/gitlab/database_importers/common_metrics/prometheus_metric.rb12
-rw-r--r--lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb40
-rw-r--r--lib/gitlab/email/hook/disable_email_interceptor.rb2
-rw-r--r--lib/gitlab/encoding_helper.rb2
-rw-r--r--lib/gitlab/git.rb5
-rw-r--r--lib/gitlab/git/repository.rb32
-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.rb6
-rw-r--r--lib/gitlab/gitaly_client.rb7
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb9
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb2
-rw-r--r--lib/gitlab/github_import/importer/lfs_objects_importer.rb2
-rw-r--r--lib/gitlab/github_import/importer/pull_requests_importer.rb2
-rw-r--r--lib/gitlab/global_id.rb14
-rw-r--r--lib/gitlab/grape_logging/loggers/perf_logger.rb19
-rw-r--r--lib/gitlab/graphql.rb4
-rw-r--r--lib/gitlab/graphql/docs/helper.rb50
-rw-r--r--lib/gitlab/graphql/docs/renderer.rb43
-rw-r--r--lib/gitlab/graphql/docs/templates/default.md.haml25
-rw-r--r--lib/gitlab/graphql/representation/submodule_tree_entry.rb34
-rw-r--r--lib/gitlab/hashed_storage/migrator.rb4
-rw-r--r--lib/gitlab/hashed_storage/rake_helper.rb6
-rw-r--r--lib/gitlab/health_checks/simple_abstract_check.rb2
-rw-r--r--lib/gitlab/import/database_helpers.rb10
-rw-r--r--lib/gitlab/import_export.rb12
-rw-r--r--lib/gitlab/import_export/attribute_cleaner.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.rb2
-rw-r--r--lib/gitlab/import_export/merge_request_parser.rb2
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb3
-rw-r--r--lib/gitlab/import_export/relation_factory.rb9
-rw-r--r--lib/gitlab/import_export/saver.rb2
-rw-r--r--lib/gitlab/import_export/version_checker.rb2
-rw-r--r--lib/gitlab/kubernetes/helm/client_command.rb14
-rw-r--r--lib/gitlab/kubernetes/helm/install_command.rb11
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb7
-rw-r--r--lib/gitlab/kubernetes/role.rb24
-rw-r--r--lib/gitlab/kubernetes/role_binding.rb7
-rw-r--r--lib/gitlab/lets_encrypt.rb11
-rw-r--r--lib/gitlab/markdown_cache/active_record/extension.rb4
-rw-r--r--lib/gitlab/markdown_cache/redis/extension.rb4
-rw-r--r--lib/gitlab/metrics/dashboard/url.rb40
-rw-r--r--lib/gitlab/metrics/samplers/base_sampler.rb2
-rw-r--r--lib/gitlab/metrics/samplers/puma_sampler.rb2
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb33
-rw-r--r--lib/gitlab/metrics/samplers/unicorn_sampler.rb11
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb15
-rw-r--r--lib/gitlab/metrics/system.rb4
-rw-r--r--lib/gitlab/middleware/read_only/controller.rb2
-rw-r--r--lib/gitlab/omniauth_initializer.rb10
-rw-r--r--lib/gitlab/pages_client.rb2
-rw-r--r--lib/gitlab/patch/active_record_query_cache.rb39
-rw-r--r--lib/gitlab/phabricator_import/cache/map.rb10
-rw-r--r--lib/gitlab/phabricator_import/conduit/user.rb31
-rw-r--r--lib/gitlab/phabricator_import/conduit/users_response.rb23
-rw-r--r--lib/gitlab/phabricator_import/issues/task_importer.rb12
-rw-r--r--lib/gitlab/phabricator_import/representation/task.rb12
-rw-r--r--lib/gitlab/phabricator_import/representation/user.rb25
-rw-r--r--lib/gitlab/phabricator_import/user_finder.rb52
-rw-r--r--lib/gitlab/push_options.rb7
-rw-r--r--lib/gitlab/reference_counter.rb4
-rw-r--r--lib/gitlab/regex.rb8
-rw-r--r--lib/gitlab/repository_cache_adapter.rb2
-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/sanitizers/exif.rb2
-rw-r--r--lib/gitlab/shell.rb12
-rw-r--r--lib/gitlab/slug/environment.rb58
-rw-r--r--lib/gitlab/submodule_links.rb26
-rw-r--r--lib/gitlab/url_blocker.rb92
-rw-r--r--lib/gitlab/usage_data.rb15
-rw-r--r--lib/gitlab/usage_data_counters/redis_counter.rb17
-rw-r--r--lib/gitlab/usage_data_counters/web_ide_counter.rb47
-rw-r--r--lib/gitlab/usage_data_counters/wiki_page_counter.rb32
-rw-r--r--lib/gitlab/web_ide_commits_counter.rb17
-rw-r--r--lib/gitlab/workhorse.rb2
-rw-r--r--lib/gitlab/zoom_link_extractor.rb21
-rw-r--r--lib/mattermost/session.rb2
-rw-r--r--lib/microsoft_teams/notifier.rb2
-rw-r--r--lib/mysql_zero_date.rb20
-rw-r--r--lib/peek/views/redis_detailed.rb (renamed from lib/peek/views/redis.rb)56
-rw-r--r--lib/peek/views/rugged.rb58
-rw-r--r--lib/prometheus/pid_provider.rb42
-rw-r--r--lib/rspec_flaky/listener.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/tasks/frontend.rake21
-rw-r--r--lib/tasks/gitlab/cleanup.rake54
-rw-r--r--lib/tasks/gitlab/db.rake13
-rw-r--r--lib/tasks/gitlab/features.rake14
-rw-r--r--lib/tasks/gitlab/graphql.rake26
-rw-r--r--lib/tasks/gitlab/seed.rake7
-rw-r--r--lib/tasks/gitlab/setup.rake1
-rw-r--r--lib/tasks/gitlab/storage.rake84
-rw-r--r--lib/tasks/karma.rake11
-rw-r--r--lib/tasks/migrate/add_limits_mysql.rake17
-rw-r--r--locale/ar_SA/gitlab.po2
-rw-r--r--locale/bg/gitlab.po2
-rw-r--r--locale/bn_BD/gitlab.po2
-rw-r--r--locale/bn_IN/gitlab.po2
-rw-r--r--locale/ca_ES/gitlab.po2
-rw-r--r--locale/cs_CZ/gitlab.po2
-rw-r--r--locale/cy_GB/gitlab.po2
-rw-r--r--locale/da_DK/gitlab.po2
-rw-r--r--locale/de/gitlab.po2
-rw-r--r--locale/el_GR/gitlab.po2
-rw-r--r--locale/eo/gitlab.po2
-rw-r--r--locale/es/gitlab.po2
-rw-r--r--locale/et_EE/gitlab.po2
-rw-r--r--locale/fil_PH/gitlab.po2
-rw-r--r--locale/fr/gitlab.po2
-rw-r--r--locale/gitlab.pot174
-rw-r--r--locale/gl_ES/gitlab.po2
-rw-r--r--locale/he_IL/gitlab.po2
-rw-r--r--locale/hi_IN/gitlab.po2
-rw-r--r--locale/hr_HR/gitlab.po2
-rw-r--r--locale/hu_HU/gitlab.po2
-rw-r--r--locale/id_ID/gitlab.po2
-rw-r--r--locale/it/gitlab.po2
-rw-r--r--locale/ja/gitlab.po2
-rw-r--r--locale/ka_GE/gitlab.po2
-rw-r--r--locale/ko/gitlab.po2
-rw-r--r--locale/mn_MN/gitlab.po2
-rw-r--r--locale/nb_NO/gitlab.po2
-rw-r--r--locale/nl_NL/gitlab.po2
-rw-r--r--locale/pa_IN/gitlab.po2
-rw-r--r--locale/pl_PL/gitlab.po2
-rw-r--r--locale/pt_BR/gitlab.po2
-rw-r--r--locale/pt_PT/gitlab.po2
-rw-r--r--locale/ro_RO/gitlab.po2
-rw-r--r--locale/ru/gitlab.po2
-rw-r--r--locale/sk_SK/gitlab.po2
-rw-r--r--locale/sq_AL/gitlab.po2
-rw-r--r--locale/sr_CS/gitlab.po2
-rw-r--r--locale/sr_SP/gitlab.po2
-rw-r--r--locale/sv_SE/gitlab.po2
-rw-r--r--locale/sw_KE/gitlab.po2
-rw-r--r--locale/tr_TR/gitlab.po2
-rw-r--r--locale/uk/gitlab.po2
-rw-r--r--locale/zh_CN/gitlab.po2
-rw-r--r--locale/zh_HK/gitlab.po2
-rw-r--r--locale/zh_TW/gitlab.po2
-rw-r--r--package.json16
-rw-r--r--qa/.rspec_parallel5
-rw-r--r--qa/Gemfile3
-rw-r--r--qa/Gemfile.lock9
-rw-r--r--qa/README.md8
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/ce/knapsack/nightly_master_report.json46
-rw-r--r--qa/qa/page/base.rb19
-rw-r--r--qa/qa/page/component/note.rb10
-rw-r--r--qa/qa/page/component/select2.rb4
-rw-r--r--qa/qa/page/element.rb4
-rw-r--r--qa/qa/page/main/login.rb10
-rw-r--r--qa/qa/page/main/menu.rb12
-rw-r--r--qa/qa/page/main/oauth.rb4
-rw-r--r--qa/qa/page/main/sign_up.rb26
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb8
-rw-r--r--qa/qa/page/project/sub_menus/common.rb6
-rw-r--r--qa/qa/page/validator.rb6
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb2
-rw-r--r--qa/qa/resource/merge_request.rb3
-rw-r--r--qa/qa/runtime/browser.rb6
-rw-r--r--qa/qa/runtime/env.rb12
-rw-r--r--qa/qa/runtime/logger.rb1
-rw-r--r--qa/qa/runtime/scenario.rb6
-rw-r--r--qa/qa/scenario/shared_attributes.rb1
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb2
-rw-r--r--qa/qa/service/kubernetes_cluster.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb8
-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/login_via_instance_wide_saml_sso_spec.rb4
-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.rb33
-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/repository/add_list_delete_branches_spec.rb2
-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_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb4
-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/add_deploy_key_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb4
-rw-r--r--qa/qa/specs/parallel_runner.rb33
-rw-r--r--qa/qa/specs/runner.rb58
-rw-r--r--qa/qa/support/retrier.rb19
-rw-r--r--qa/qa/tools/generate_perf_testdata.rb65
-rw-r--r--qa/spec/page/element_spec.rb13
-rw-r--r--qa/spec/page/logging_spec.rb8
-rw-r--r--qa/spec/spec_helper.rb4
-rw-r--r--qa/spec/specs/parallel_runner_spec.rb58
-rw-r--r--qa/spec/specs/runner_spec.rb12
-rw-r--r--rubocop/cop/gitlab/rails_logger.rb51
-rw-r--r--rubocop/cop/qa/element_with_pattern.rb2
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--scripts/create_mysql_user.sh7
-rwxr-xr-xscripts/gather-test-memory-data21
-rwxr-xr-xscripts/lint-changelog-yaml24
-rwxr-xr-xscripts/lint-doc.sh4
-rwxr-xr-xscripts/lint-rugged9
-rwxr-xr-xscripts/merge-html-reports84
-rwxr-xr-xscripts/review_apps/review-apps.sh9
-rwxr-xr-xscripts/trigger-build-docs6
-rw-r--r--spec/controllers/admin/application_settings_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/runners_controller_spec.rb5
-rw-r--r--spec/controllers/admin/users_controller_spec.rb6
-rw-r--r--spec/controllers/application_controller_spec.rb2
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb22
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb217
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb4
-rw-r--r--spec/controllers/chaos_controller_spec.rb127
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb15
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb4
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/health_check_controller_spec.rb1
-rw-r--r--spec/controllers/health_controller_spec.rb1
-rw-r--r--spec/controllers/ide_controller_spec.rb17
-rw-r--r--spec/controllers/metrics_controller_spec.rb1
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb4
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb4
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb9
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb4
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb9
-rw-r--r--spec/controllers/projects/cycle_analytics/events_controller_spec.rb64
-rw-r--r--spec/controllers/projects/deploy_keys_controller_spec.rb10
-rw-r--r--spec/controllers/projects/discussions_controller_spec.rb4
-rw-r--r--spec/controllers/projects/environments/prometheus_api_controller_spec.rb2
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb199
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb5
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb20
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb42
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb69
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb4
-rw-r--r--spec/controllers/projects/templates_controller_spec.rb9
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb43
-rw-r--r--spec/controllers/projects_controller_spec.rb29
-rw-r--r--spec/controllers/snippets/notes_controller_spec.rb6
-rw-r--r--spec/controllers/snippets_controller_spec.rb2
-rw-r--r--spec/controllers/users_controller_spec.rb2
-rw-r--r--spec/factories/ci/bridge.rb2
-rw-r--r--spec/factories/clusters/clusters.rb2
-rw-r--r--spec/factories/groups.rb4
-rw-r--r--spec/factories/lfs_objects.rb1
-rw-r--r--spec/factories/pages_domains.rb84
-rw-r--r--spec/factories/projects.rb26
-rw-r--r--spec/factories/services.rb18
-rw-r--r--spec/factories/services_data.rb8
-rw-r--r--spec/features/admin/admin_groups_spec.rb9
-rw-r--r--spec/features/admin/admin_projects_spec.rb1
-rw-r--r--spec/features/admin/admin_requests_profiles_spec.rb115
-rw-r--r--spec/features/admin/admin_sees_project_statistics_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb35
-rw-r--r--spec/features/dashboard/milestones_spec.rb14
-rw-r--r--spec/features/groups/group_settings_spec.rb8
-rw-r--r--spec/features/groups/show_spec.rb99
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb2
-rw-r--r--spec/features/issues/user_comments_on_issue_spec.rb5
-rw-r--r--spec/features/markdown/mermaid_spec.rb18
-rw-r--r--spec/features/merge_request/user_creates_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb6
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb1
-rw-r--r--spec/features/projects/fork_spec.rb2
-rw-r--r--spec/features/projects/new_project_spec.rb432
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb4
-rw-r--r--spec/features/projects/tree/create_file_spec.rb4
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb124
-rw-r--r--spec/features/triggers_spec.rb78
-rw-r--r--spec/finders/autocomplete/move_to_project_finder_spec.rb40
-rw-r--r--spec/fixtures/api/schemas/current-board.json16
-rw-r--r--spec/fixtures/phabricator_responses/user.search.json62
-rw-r--r--spec/fixtures/security-reports/deprecated/gl-sast-report.json20
-rw-r--r--spec/fixtures/security-reports/master/gl-sast-report.json20
-rw-r--r--spec/frontend/api_spec.js16
-rw-r--r--spec/frontend/behaviors/markdown/render_metrics_spec.js37
-rw-r--r--spec/frontend/boards/services/board_service_spec.js159
-rw-r--r--spec/frontend/commons/nav/user_merge_requests_spec.js113
-rw-r--r--spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap12
-rw-r--r--spec/frontend/create_merge_request_dropdown_spec.js34
-rw-r--r--spec/frontend/environment.js8
-rw-r--r--spec/frontend/error_tracking_settings/mock.js2
-rw-r--r--spec/frontend/filterable_list_spec.js53
-rw-r--r--spec/frontend/fixtures/abuse_reports.rb (renamed from spec/javascripts/fixtures/abuse_reports.rb)2
-rw-r--r--spec/frontend/fixtures/admin_users.rb (renamed from spec/javascripts/fixtures/admin_users.rb)2
-rw-r--r--spec/frontend/fixtures/application_settings.rb (renamed from spec/javascripts/fixtures/application_settings.rb)2
-rw-r--r--spec/frontend/fixtures/autocomplete_sources.rb (renamed from spec/javascripts/fixtures/autocomplete_sources.rb)2
-rw-r--r--spec/frontend/fixtures/blob.rb (renamed from spec/javascripts/fixtures/blob.rb)2
-rw-r--r--spec/frontend/fixtures/boards.rb (renamed from spec/javascripts/fixtures/boards.rb)2
-rw-r--r--spec/frontend/fixtures/branches.rb (renamed from spec/javascripts/fixtures/branches.rb)2
-rw-r--r--spec/frontend/fixtures/clusters.rb (renamed from spec/javascripts/fixtures/clusters.rb)2
-rw-r--r--spec/frontend/fixtures/commit.rb (renamed from spec/javascripts/fixtures/commit.rb)2
-rw-r--r--spec/frontend/fixtures/deploy_keys.rb (renamed from spec/javascripts/fixtures/deploy_keys.rb)2
-rw-r--r--spec/frontend/fixtures/groups.rb (renamed from spec/javascripts/fixtures/groups.rb)4
-rw-r--r--spec/frontend/fixtures/issues.rb (renamed from spec/javascripts/fixtures/issues.rb)6
-rw-r--r--spec/frontend/fixtures/jobs.rb (renamed from spec/javascripts/fixtures/jobs.rb)4
-rw-r--r--spec/frontend/fixtures/labels.rb (renamed from spec/javascripts/fixtures/labels.rb)4
-rw-r--r--spec/frontend/fixtures/merge_requests.rb (renamed from spec/javascripts/fixtures/merge_requests.rb)2
-rw-r--r--spec/frontend/fixtures/merge_requests_diffs.rb (renamed from spec/javascripts/fixtures/merge_requests_diffs.rb)3
-rw-r--r--spec/frontend/fixtures/pipeline_schedules.rb (renamed from spec/javascripts/fixtures/pipeline_schedules.rb)4
-rw-r--r--spec/frontend/fixtures/pipelines.rb (renamed from spec/javascripts/fixtures/pipelines.rb)2
-rw-r--r--spec/frontend/fixtures/projects.rb (renamed from spec/javascripts/fixtures/projects.rb)12
-rw-r--r--spec/frontend/fixtures/prometheus_service.rb (renamed from spec/javascripts/fixtures/prometheus_service.rb)2
-rw-r--r--spec/frontend/fixtures/raw.rb (renamed from spec/javascripts/fixtures/raw.rb)0
-rw-r--r--spec/frontend/fixtures/search.rb (renamed from spec/javascripts/fixtures/search.rb)2
-rw-r--r--spec/frontend/fixtures/services.rb (renamed from spec/javascripts/fixtures/services.rb)2
-rw-r--r--spec/frontend/fixtures/sessions.rb (renamed from spec/javascripts/fixtures/sessions.rb)2
-rw-r--r--spec/frontend/fixtures/snippet.rb (renamed from spec/javascripts/fixtures/snippet.rb)2
-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.rb (renamed from spec/javascripts/fixtures/todos.rb)4
-rw-r--r--spec/frontend/fixtures/u2f.rb (renamed from spec/javascripts/fixtures/u2f.rb)4
-rw-r--r--spec/frontend/helpers/fixtures.js7
-rw-r--r--spec/frontend/ide/services/index_spec.js32
-rw-r--r--spec/frontend/issue_show/components/pinned_links_spec.js52
-rw-r--r--spec/frontend/mocks/ce/lib/utils/axios_utils.js15
-rw-r--r--spec/frontend/mocks/mocks_helper.js60
-rw-r--r--spec/frontend/mocks/mocks_helper_spec.js147
-rw-r--r--spec/frontend/mocks/node/jquery.js13
-rw-r--r--spec/frontend/mocks_spec.js13
-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/operation_settings/components/external_dashboard_spec.js5
-rw-r--r--spec/frontend/projects/projects_filterable_list_spec.js31
-rw-r--r--spec/frontend/repository/components/breadcrumbs_spec.js20
-rw-r--r--spec/frontend/repository/components/table/row_spec.js16
-rw-r--r--spec/frontend/test_setup.js22
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js5
-rw-r--r--spec/graphql/gitlab_schema_spec.rb20
-rw-r--r--spec/graphql/types/diff_refs_type_spec.rb9
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/diff_position_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/discussion_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/submodule_type_spec.rb2
-rw-r--r--spec/helpers/avatars_helper_spec.rb2
-rw-r--r--spec/helpers/blob_helper_spec.rb38
-rw-r--r--spec/helpers/boards_helper_spec.rb16
-rw-r--r--spec/helpers/dashboard_helper_spec.rb37
-rw-r--r--spec/helpers/emails_helper_spec.rb2
-rw-r--r--spec/helpers/icons_helper_spec.rb20
-rw-r--r--spec/helpers/issuables_helper_spec.rb41
-rw-r--r--spec/helpers/markup_helper_spec.rb73
-rw-r--r--spec/helpers/sorting_helper_spec.rb148
-rw-r--r--spec/helpers/storage_helper_spec.rb2
-rw-r--r--spec/helpers/submodule_helper_spec.rb81
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb72
-rw-r--r--spec/javascripts/boards/board_blank_state_spec.js19
-rw-r--r--spec/javascripts/boards/boards_store_spec.js13
-rw-r--r--spec/javascripts/boards/components/board_form_spec.js56
-rw-r--r--spec/javascripts/boards/components/boards_selector_spec.js205
-rw-r--r--spec/javascripts/fixtures/.gitignore7
-rw-r--r--spec/javascripts/fixtures/static/README.md3
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js18
-rw-r--r--spec/javascripts/helpers/vuex_action_helper.js4
-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/issuable_spec.js12
-rw-r--r--spec/javascripts/monitoring/charts/empty_chart_spec.js29
-rw-r--r--spec/javascripts/monitoring/charts/single_stat_spec.js9
-rw-r--r--spec/javascripts/monitoring/mock_data.js72
-rw-r--r--spec/javascripts/monitoring/panel_type_spec.js44
-rw-r--r--spec/javascripts/monitoring/store/mutations_spec.js2
-rw-r--r--spec/javascripts/monitoring/utils_spec.js27
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js15
-rw-r--r--spec/javascripts/notes/components/note_actions/reply_button_spec.js5
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js20
-rw-r--r--spec/javascripts/registry/components/app_spec.js2
-rw-r--r--spec/javascripts/registry/components/collapsible_container_spec.js2
-rw-r--r--spec/javascripts/registry/components/table_registry_spec.js2
-rw-r--r--spec/javascripts/test_bundle.js1
-rw-r--r--spec/javascripts/test_constants.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js3
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb10
-rw-r--r--spec/lib/banzai/filter/inline_metrics_filter_spec.rb55
-rw-r--r--spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb58
-rw-r--r--spec/lib/banzai/filter/reference_redactor_filter_spec.rb (renamed from spec/lib/banzai/filter/redactor_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb8
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb6
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb (renamed from spec/lib/banzai/redactor_spec.rb)2
-rw-r--r--spec/lib/banzai/renderer_spec.rb18
-rw-r--r--spec/lib/forever_spec.rb16
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb218
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb14
-rw-r--r--spec/lib/gitlab/auth_spec.rb64
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb104
-rw-r--r--spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb98
-rw-r--r--spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb23
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb26
-rw-r--r--spec/lib/gitlab/batch_pop_queueing_spec.rb147
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb44
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb36
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb52
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb246
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb10
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb6
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb98
-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_stage_spec.rb96
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb78
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_stage_spec.rb68
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_event_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb70
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb34
-rw-r--r--spec/lib/gitlab/cycle_analytics/usage_data_spec.rb80
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb30
-rw-r--r--spec/lib/gitlab/database/median_spec.rb17
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb521
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb (renamed from spec/db/importers/common_metrics_importer_spec.rb)25
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb16
-rw-r--r--spec/lib/gitlab/database_spec.rb135
-rw-r--r--spec/lib/gitlab/email/handler/create_issue_handler_spec.rb1
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb22
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb26
-rw-r--r--spec/lib/gitlab/git_spec.rb20
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb24
-rw-r--r--spec/lib/gitlab/global_id_spec.rb37
-rw-r--r--spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb30
-rw-r--r--spec/lib/gitlab/import/database_helpers_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/attribute_cleaner_spec.rb10
-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.json11
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb11
-rw-r--r--spec/lib/gitlab/import_export/relation_rename_service_spec.rb1
-rw-r--r--spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb16
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb12
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb3
-rw-r--r--spec/lib/gitlab/kubernetes/role_binding_spec.rb4
-rw-r--r--spec/lib/gitlab/kubernetes/role_spec.rb30
-rw-r--r--spec/lib/gitlab/lets_encrypt_spec.rb31
-rw-r--r--spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb7
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/url_spec.rb56
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb33
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb10
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb28
-rw-r--r--spec/lib/gitlab/phabricator_import/cache/map_spec.rb17
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/user_spec.rb49
-rw-r--r--spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb21
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/importer_spec.rb32
-rw-r--r--spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb36
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/task_spec.rb16
-rw-r--r--spec/lib/gitlab/phabricator_import/representation/user_spec.rb28
-rw-r--r--spec/lib/gitlab/phabricator_import/user_finder_spec.rb89
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb4
-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/slug/environment_spec.rb38
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb4
-rw-r--r--spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb33
-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.rb69
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb34
-rw-r--r--spec/lib/gitlab/web_ide_commits_counter_spec.rb19
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb1
-rw-r--r--spec/lib/gitlab/zoom_link_extractor_spec.rb24
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb39
-rw-r--r--spec/lib/peek/views/rugged_spec.rb41
-rw-r--r--spec/lib/prometheus/pid_provider_spec.rb79
-rw-r--r--spec/lib/serializers/json_spec.rb84
-rw-r--r--spec/mailers/emails/pages_domains_spec.rb6
-rw-r--r--spec/migrations/active_record/schema_spec.rb2
-rw-r--r--spec/migrations/fix_wrong_pages_access_level_spec.rb97
-rw-r--r--spec/models/active_session_spec.rb15
-rw-r--r--spec/models/ci/build_spec.rb29
-rw-r--r--spec/models/ci/job_artifact_spec.rb25
-rw-r--r--spec/models/ci/runner_spec.rb4
-rw-r--r--spec/models/ci/trigger_spec.rb20
-rw-r--r--spec/models/clusters/applications/runner_spec.rb33
-rw-r--r--spec/models/commit_range_spec.rb12
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb30
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb27
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb28
-rw-r--r--spec/models/concerns/issuable_spec.rb21
-rw-r--r--spec/models/concerns/project_api_compatibility_spec.rb59
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb18
-rw-r--r--spec/models/concerns/routable_spec.rb47
-rw-r--r--spec/models/concerns/stepable_spec.rb111
-rw-r--r--spec/models/cycle_analytics/code_spec.rb7
-rw-r--r--spec/models/cycle_analytics/group_level_spec.rb46
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb7
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb7
-rw-r--r--spec/models/cycle_analytics/production_spec.rb7
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb (renamed from spec/models/cycle_analytics_spec.rb)10
-rw-r--r--spec/models/cycle_analytics/review_spec.rb5
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb6
-rw-r--r--spec/models/cycle_analytics/test_spec.rb11
-rw-r--r--spec/models/discussion_spec.rb14
-rw-r--r--spec/models/environment_spec.rb26
-rw-r--r--spec/models/group_spec.rb44
-rw-r--r--spec/models/issue/metrics_spec.rb6
-rw-r--r--spec/models/merge_request_spec.rb23
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/pages_domain_spec.rb53
-rw-r--r--spec/models/project_feature_spec.rb28
-rw-r--r--spec/models/project_services/bugzilla_service_spec.rb6
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb6
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb6
-rw-r--r--spec/models/project_services/jira_service_spec.rb30
-rw-r--r--spec/models/project_services/redmine_service_spec.rb6
-rw-r--r--spec/models/project_services/youtrack_service_spec.rb6
-rw-r--r--spec/models/project_spec.rb42
-rw-r--r--spec/models/remote_mirror_spec.rb15
-rw-r--r--spec/models/repository_spec.rb7
-rw-r--r--spec/models/user_spec.rb65
-rw-r--r--spec/models/wiki_page_spec.rb3
-rw-r--r--spec/policies/ci/trigger_policy_spec.rb94
-rw-r--r--spec/policies/group_policy_spec.rb127
-rw-r--r--spec/policies/project_policy_spec.rb120
-rw-r--r--spec/presenters/blobs/unfold_presenter_spec.rb6
-rw-r--r--spec/requests/api/commit_statuses_spec.rb24
-rw-r--r--spec/requests/api/commits_spec.rb205
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb64
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb70
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb64
-rw-r--r--spec/requests/api/graphql/mutations/notes/destroy_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/notes/update_spec.rb72
-rw-r--r--spec/requests/api/graphql_spec.rb10
-rw-r--r--spec/requests/api/group_clusters_spec.rb452
-rw-r--r--spec/requests/api/groups_spec.rb4
-rw-r--r--spec/requests/api/internal_spec.rb12
-rw-r--r--spec/requests/api/project_clusters_spec.rb16
-rw-r--r--spec/requests/api/projects_spec.rb103
-rw-r--r--spec/requests/api/search_spec.rb4
-rw-r--r--spec/requests/api/services_spec.rb13
-rw-r--r--spec/requests/api/user_counts_spec.rb40
-rw-r--r--spec/requests/api/users_spec.rb16
-rw-r--r--spec/requests/lfs_http_spec.rb4
-rw-r--r--spec/requests/lfs_locks_api_spec.rb4
-rw-r--r--spec/requests/request_profiler_spec.rb20
-rw-r--r--spec/routing/api_routing_spec.rb23
-rw-r--r--spec/routing/environments_spec.rb9
-rw-r--r--spec/routing/project_routing_spec.rb6
-rw-r--r--spec/rubocop/cop/gitlab/rails_logger_spec.rb42
-rw-r--r--spec/rubocop/cop/qa/element_with_pattern_spec.rb4
-rw-r--r--spec/serializers/analytics_issue_entity_spec.rb6
-rw-r--r--spec/serializers/analytics_issue_serializer_spec.rb6
-rw-r--r--spec/serializers/analytics_merge_request_serializer_spec.rb6
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb10
-rw-r--r--spec/serializers/diff_file_base_entity_spec.rb26
-rw-r--r--spec/serializers/environment_status_entity_spec.rb2
-rw-r--r--spec/services/audit_event_service_spec.rb32
-rw-r--r--spec/services/boards/issues/move_service_spec.rb86
-rw-r--r--spec/services/ci/archive_trace_service_spec.rb38
-rw-r--r--spec/services/clusters/applications/check_uninstall_progress_service_spec.rb8
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb2
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb20
-rw-r--r--spec/services/groups/create_service_spec.rb8
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb502
-rw-r--r--spec/services/issuable/clone/content_rewriter_spec.rb13
-rw-r--r--spec/services/issues/update_service_spec.rb18
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb6
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb13
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb13
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb8
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb76
-rw-r--r--spec/services/merge_requests/update_service_spec.rb4
-rw-r--r--spec/services/notification_service_spec.rb104
-rw-r--r--spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb6
-rw-r--r--spec/services/projects/update_service_spec.rb4
-rw-r--r--spec/services/self_monitoring/project/create_service_spec.rb201
-rw-r--r--spec/services/submodules/update_service_spec.rb2
-rw-r--r--spec/services/task_list_toggle_service_spec.rb17
-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/spec_helper.rb20
-rw-r--r--spec/support/capybara.rb1
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb12
-rw-r--r--spec/support/database_cleaner.rb27
-rw-r--r--spec/support/db_cleaner.rb4
-rw-r--r--spec/support/helpers/expect_request_with_status.rb11
-rw-r--r--spec/support/helpers/graphql_helpers.rb15
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb2
-rw-r--r--spec/support/helpers/key_generator_helper.rb2
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb5
-rw-r--r--spec/support/helpers/memory_usage_helper.rb37
-rw-r--r--spec/support/helpers/migrations_helpers.rb13
-rw-r--r--spec/support/helpers/stub_configuration.rb4
-rw-r--r--spec/support/helpers/test_env.rb38
-rw-r--r--spec/support/json_response.rb2
-rw-r--r--spec/support/matchers/abort_matcher.rb46
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb2
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb61
-rw-r--r--spec/support/shared_examples/mentionable_shared_examples.rb51
-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/taskable_shared_examples.rb21
-rw-r--r--spec/support/shared_examples/update_invalid_issuable.rb2
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb30
-rw-r--r--spec/tasks/gitlab/storage_rake_spec.rb34
-rw-r--r--spec/validators/qualified_domain_array_validator_spec.rb111
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb13
-rw-r--r--spec/views/notify/pipeline_failed_email.html.haml_spec.rb4
-rw-r--r--spec/views/notify/pipeline_failed_email.text.erb_spec.rb2
-rw-r--r--spec/views/notify/pipeline_success_email.html.haml_spec.rb4
-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/stuck_ci_jobs_worker_spec.rb4
-rw-r--r--yarn.lock2364
1909 files changed, 33020 insertions, 15691 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index b865b212ac0..13c8b4a8458 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -9,8 +9,12 @@
app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya @pslaughter
*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya @pslaughter
-# Someone from the database team should review changes in `db/`
+# Maintainers from the Database team should review changes in `db/`
db/ @abrandl @NikolayS
+lib/gitlab/background_migration/ @abrandl @NikolayS
+lib/gitlab/database/ @abrandl @NikolayS
+lib/gitlab/sql/ @abrandl @NikolayS
+/ee/db/ @abrandl @NikolayS
# 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..de1110f39fa 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -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..fe369ffec13 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -113,37 +113,6 @@ compile-assets pull-cache:
- 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
-
karma:
extends: .dedicated-no-docs-pull-cache-job
<<: *use-pg
@@ -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/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/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 2d06a8acc58..1392768127b 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -66,6 +66,8 @@
- scripts/gitaly-test-spawn
- date
- 'export KNAPSACK_TEST_FILE_PATTERN=$(ruby -r./lib/quality/test_level.rb -e "puts Quality::TestLevel.new.pattern(:${TEST_LEVEL})")'
+ - mkdir -p tmp/memory_test
+ - export MEMORY_TEST_PATH="tmp/memory_test/${TEST_TOOL}_${TEST_LEVEL}_${DATABASE}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_memory.csv"
- knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml --tag level:${TEST_LEVEL} --tag ~geo"
- date
artifacts:
@@ -77,8 +79,9 @@
- rspec_flaky/
- rspec_profiling/
- tmp/capybara/
-# reports:
-# junit: junit_rspec.xml
+ - tmp/memory_test/
+ reports:
+ junit: junit_rspec.xml
.rspec-metadata-pg: &rspec-metadata-pg
<<: *rspec-metadata
@@ -273,6 +276,7 @@ coverage:
stage: post-test
script:
- bundle exec scripts/merge-simplecov
+ - bundle exec scripts/gather-test-memory-data
coverage: '/LOC \((\d+\.\d+%)\) covered.$/'
artifacts:
name: coverage
@@ -280,3 +284,4 @@ coverage:
paths:
- coverage/index.html
- coverage/assets/
+ - tmp/memory_test/
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index 89b5ae38072..ca55bbd32a7 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -10,17 +10,17 @@ code_quality:
tags: []
before_script: []
cache: {}
- dependencies: []
sast:
extends: .dedicated-no-docs
- before_script: []
tags: []
+ before_script: []
+ cache: {}
variables:
- SAST_CONFIDENCE_LEVEL: 2
- DOCKER_DRIVER: overlay2
+ SAST_BRAKEMAN_LEVEL: 2
dependency_scanning:
- before_script: []
- tags: []
extends: .dedicated-no-docs
+ tags: []
+ before_script: []
+ cache: {}
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index ce019de213b..41d52c4e095 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -174,7 +174,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
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
index 2454ea85652..3a5735a2be9 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
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/Documentation.md b/.gitlab/issue_templates/Documentation.md
index 67602b7b2df..f4070a44755 100644
--- a/.gitlab/issue_templates/Documentation.md
+++ b/.gitlab/issue_templates/Documentation.md
@@ -4,7 +4,7 @@
Note: Doc work as part of feature development is covered in the Feature Request template.
* For issues related to features of the docs.gitlab.com site, see
- https://gitlab.com/gitlab-com/gitlab-docs/issues/
+ https://gitlab.com/gitlab-org/gitlab-docs/issues/
* For information about documentation content and process, see
https://docs.gitlab.com/ee/development/documentation/ -->
diff --git a/.gitlab/merge_request_templates/Change documentation location.md b/.gitlab/merge_request_templates/Change documentation location.md
index c80af95d5e5..7dc80a641c4 100644
--- a/.gitlab/merge_request_templates/Change documentation location.md
+++ b/.gitlab/merge_request_templates/Change documentation location.md
@@ -22,7 +22,7 @@ https://docs.gitlab.com/ce/development/documentation/index.html#changing-documen
- [ ] Make sure internal links pointing to the document in question are not broken.
- [ ] Search and replace any links referring to old docs in GitLab Rails app,
specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
-- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments)
+- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/documentation/index.html#redirections-for-pages-with-disqus-comments)
to the new document if there are any Disqus comments on the old document thread.
- [ ] Update the link in `features.yml` (if applicable)
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
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/.mdlrc b/.mdlrc
index b2127dadc22..151c54f7d44 100644
--- a/.mdlrc
+++ b/.mdlrc
@@ -1,3 +1,6 @@
+# This is the options file for mdl, configured in .gitlab/ci/docs.gitlab-ci.yml,
+# and related to the style file ./mdlrc.style
+
# See https://github.com/markdownlint/markdownlint/blob/master/docs/configuration.md
ignore_front_matter true
diff --git a/.mdlrc.style b/.mdlrc.style
index 30abf03f462..0ca3611df0b 100644
--- a/.mdlrc.style
+++ b/.mdlrc.style
@@ -1,7 +1,21 @@
+# This is the style file for mdl, configured in .gitlab/ci/docs.gitlab-ci.yml,
+# and related to the options file ./mdlrc
+
# See https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md
+# for more detailed information on the rules and styles.
+
+rule "MD001"
+rule "MD003", :style => :atx
+rule "MD011"
+rule "MD023"
+rule "MD032"
+rule "MD034"
+rule "MD037"
+
+# Should not be used currently:
-rule 'MD001'
+# rule "MD004", :style => :dash # unordered list style - dash
# False positives, see https://github.com/markdownlint/markdownlint/issues/261
-# rule 'MD004', style: :dash
-rule 'MD032'
-rule 'MD034'
+
+# rule "MD039" # Spaces inside link text
+# Crashes when link text has certain punctuation
diff --git a/.rubocop.yml b/.rubocop.yml
index e5fe527e611..79e06439ac2 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -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/CHANGELOG.md b/CHANGELOG.md
index 0b73585722f..3ed244b7ed2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,281 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 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.3 (2019-06-27)
- No changes.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index ba0a719118c..3f4830156cb 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.51.0
+1.53.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 9c6d6293b1a..bd8bf882d06 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.6.1
+1.7.0
diff --git a/Gemfile b/Gemfile
index 33f9c4650ef..ba958c6ae10 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,8 @@
source 'https://rubygems.org'
-gem 'rails', '5.1.7'
+gem 'rails', '5.2.3'
+
+gem 'bootsnap', '~> 1.4'
# Improves copy-on-write performance for MRI
gem 'nakayoshi_fork', '~> 0.0.4'
@@ -14,7 +16,6 @@ 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 'rugged', '~> 0.28'
@@ -84,6 +85,7 @@ gem 'rack-cors', '~> 1.0.0', require: 'rack/cors'
gem 'graphql', '~> 1.8.0'
gem 'graphiql-rails', '~> 1.4.10'
gem 'apollo_upload_server', '~> 2.0.0.beta3'
+gem 'graphql-docs', '~> 1.6.0', group: [:development, :test]
# Disable strong_params so that Mash does not respond to :permitted?
gem 'hashie-forbidden_attributes'
@@ -99,7 +101,7 @@ gem 'carrierwave', '~> 1.3'
gem 'mini_magick'
# for backups
-gem 'fog-aws', '~> 3.3'
+gem 'fog-aws', '~> 3.5'
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
# Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0'
@@ -295,10 +297,8 @@ 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'
# Memory benchmarks
gem 'derailed_benchmarks', require: false
@@ -329,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'
@@ -339,7 +338,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.7.0'
gem 'factory_bot_rails', '~> 4.8.2'
- gem 'rspec-rails', '~> 3.7.0'
+ gem 'rspec-rails', '~> 3.8.0'
gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
@@ -420,7 +419,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
-gem 'net-ssh', '~> 5.0'
+gem 'net-ssh', '~> 5.2'
gem 'sshkey', '~> 2.0'
# Required for ED25519 SSH host key support
@@ -430,7 +429,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.36.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 1.37.0', require: 'gitaly'
gem 'grpc', '~> 1.19.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index c8b8f0e4f90..7e26c5bbc45 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -6,44 +6,48 @@ GEM
ace-rails-ap (4.1.2)
acme-client (2.0.2)
faraday (~> 0.9, >= 0.9.1)
- actioncable (5.1.7)
- actionpack (= 5.1.7)
+ actioncable (5.2.3)
+ actionpack (= 5.2.3)
nio4r (~> 2.0)
- websocket-driver (~> 0.6.1)
- actionmailer (5.1.7)
- actionpack (= 5.1.7)
- actionview (= 5.1.7)
- activejob (= 5.1.7)
+ websocket-driver (>= 0.6.1)
+ actionmailer (5.2.3)
+ actionpack (= 5.2.3)
+ actionview (= 5.2.3)
+ activejob (= 5.2.3)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
- actionpack (5.1.7)
- actionview (= 5.1.7)
- activesupport (= 5.1.7)
+ actionpack (5.2.3)
+ actionview (= 5.2.3)
+ activesupport (= 5.2.3)
rack (~> 2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (5.1.7)
- activesupport (= 5.1.7)
+ actionview (5.2.3)
+ activesupport (= 5.2.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
- activejob (5.1.7)
- activesupport (= 5.1.7)
+ activejob (5.2.3)
+ activesupport (= 5.2.3)
globalid (>= 0.3.6)
- activemodel (5.1.7)
- activesupport (= 5.1.7)
- activerecord (5.1.7)
- activemodel (= 5.1.7)
- activesupport (= 5.1.7)
- arel (~> 8.0)
+ activemodel (5.2.3)
+ activesupport (= 5.2.3)
+ activerecord (5.2.3)
+ activemodel (= 5.2.3)
+ activesupport (= 5.2.3)
+ arel (>= 9.0)
activerecord-explain-analyze (0.1.0)
activerecord (>= 4)
pg
activerecord_sane_schema_dumper (1.0)
rails (>= 5, < 6)
- activesupport (5.1.7)
+ activestorage (5.2.3)
+ actionpack (= 5.2.3)
+ activerecord (= 5.2.3)
+ marcel (~> 0.3.1)
+ activesupport (5.2.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@@ -60,7 +64,7 @@ GEM
apollo_upload_server (2.0.0.beta.3)
graphql (>= 1.8)
rails (>= 4.2)
- arel (8.0.0)
+ arel (9.0.0)
asana (0.8.1)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
@@ -72,7 +76,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)
@@ -97,7 +100,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)
@@ -216,6 +219,8 @@ GEM
excon (0.62.0)
execjs (2.6.0)
expression_parser (0.9.0)
+ extended-markdown-filter (0.6.0)
+ html-pipeline (~> 2.0)
factory_bot (4.8.2)
activesupport (>= 3.0.0)
factory_bot_rails (4.8.2)
@@ -247,7 +252,7 @@ GEM
fog-json
ipaddress (~> 0.8)
xml-simple (~> 1.1)
- fog-aws (3.3.0)
+ fog-aws (3.5.2)
fog-core (~> 2.1)
fog-json (~> 1.1)
fog-xml (~> 0.1)
@@ -290,6 +295,7 @@ GEM
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
+ gemoji (3.0.1)
gemojione (3.3.0)
json
get_process_mem (0.2.3)
@@ -303,7 +309,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.36.0)
+ gitaly-proto (1.37.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-labkit (0.3.0)
@@ -370,6 +376,14 @@ GEM
railties
sprockets-rails
graphql (1.8.1)
+ graphql-docs (1.6.0)
+ commonmarker (~> 0.16)
+ escape_utils (~> 1.2)
+ extended-markdown-filter (~> 0.4)
+ gemoji (~> 3.0)
+ graphql (~> 1.6)
+ html-pipeline (~> 2.8)
+ sass (~> 3.4)
grpc (1.19.0)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
@@ -493,6 +507,8 @@ GEM
mail (2.7.1)
mini_mime (>= 0.1.1)
mail_room (0.9.1)
+ marcel (0.3.3)
+ mimemagic (~> 0.3.2)
mdl (0.5.0)
kramdown (~> 1.12, >= 1.12.0)
mixlib-cli (~> 1.7, >= 1.7.0)
@@ -513,17 +529,16 @@ GEM
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.0.1)
+ net-ssh (5.2.0)
netrc (0.11.0)
nio4r (2.3.1)
nokogiri (1.10.3)
@@ -628,11 +643,6 @@ GEM
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
@@ -641,10 +651,6 @@ GEM
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)
@@ -695,17 +701,18 @@ GEM
rack-test (1.1.0)
rack (>= 1.0, < 3)
rack-timeout (0.5.1)
- rails (5.1.7)
- actioncable (= 5.1.7)
- actionmailer (= 5.1.7)
- actionpack (= 5.1.7)
- actionview (= 5.1.7)
- activejob (= 5.1.7)
- activemodel (= 5.1.7)
- activerecord (= 5.1.7)
- activesupport (= 5.1.7)
+ rails (5.2.3)
+ actioncable (= 5.2.3)
+ actionmailer (= 5.2.3)
+ actionpack (= 5.2.3)
+ actionview (= 5.2.3)
+ activejob (= 5.2.3)
+ activemodel (= 5.2.3)
+ activerecord (= 5.2.3)
+ activestorage (= 5.2.3)
+ activesupport (= 5.2.3)
bundler (>= 1.3.0)
- railties (= 5.1.7)
+ railties (= 5.2.3)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2)
actionpack (~> 5.x, >= 5.0.1)
@@ -719,12 +726,12 @@ GEM
rails-i18n (5.1.1)
i18n (>= 0.7, < 2)
railties (>= 5.0, < 6)
- railties (5.1.7)
- actionpack (= 5.1.7)
- activesupport (= 5.1.7)
+ railties (5.2.3)
+ actionpack (= 5.2.3)
+ activesupport (= 5.2.3)
method_source
rake (>= 0.8.7)
- thor (>= 0.18.1, < 2.0)
+ thor (>= 0.19.0, < 2.0)
rainbow (3.0.0)
raindrops (0.19.0)
rake (12.3.2)
@@ -783,36 +790,36 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- rspec (3.7.0)
- rspec-core (~> 3.7.0)
- rspec-expectations (~> 3.7.0)
- rspec-mocks (~> 3.7.0)
- rspec-core (3.7.1)
- rspec-support (~> 3.7.0)
- rspec-expectations (3.7.0)
+ rspec (3.8.0)
+ rspec-core (~> 3.8.0)
+ rspec-expectations (~> 3.8.0)
+ rspec-mocks (~> 3.8.0)
+ rspec-core (3.8.2)
+ rspec-support (~> 3.8.0)
+ rspec-expectations (3.8.4)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.7.0)
- rspec-mocks (3.7.0)
+ rspec-support (~> 3.8.0)
+ rspec-mocks (3.8.1)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.7.0)
+ rspec-support (~> 3.8.0)
rspec-parameterized (0.4.2)
binding_ninja (>= 0.2.3)
parser
proc_to_ast
rspec (>= 2.13, < 4)
unparser
- rspec-rails (3.7.2)
+ rspec-rails (3.8.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
- rspec-core (~> 3.7.0)
- rspec-expectations (~> 3.7.0)
- rspec-mocks (~> 3.7.0)
- rspec-support (~> 3.7.0)
+ rspec-core (~> 3.8.0)
+ rspec-expectations (~> 3.8.0)
+ rspec-mocks (~> 3.8.0)
+ rspec-support (~> 3.8.0)
rspec-retry (0.6.1)
rspec-core (> 3.3)
rspec-set (0.1.3)
- rspec-support (3.7.1)
+ rspec-support (3.8.2)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rspec_profiling (0.0.5)
@@ -1008,7 +1015,7 @@ GEM
hashdiff
webpack-rails (0.9.11)
railties (>= 3.2.0)
- websocket-driver (0.6.5)
+ websocket-driver (0.7.0)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
wikicloth (0.8.1)
@@ -1087,7 +1094,7 @@ DEPENDENCIES
flipper-active_support_cache_store (~> 0.13.0)
flowdock (~> 0.7)
fog-aliyun (~> 0.3)
- fog-aws (~> 3.3)
+ fog-aws (~> 3.5)
fog-core (= 2.1.0)
fog-google (~> 1.8)
fog-local (~> 0.6)
@@ -1101,7 +1108,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.36.0)
+ gitaly-proto (~> 1.37.0)
github-markup (~> 1.7.0)
gitlab-labkit (~> 0.3.0)
gitlab-markup (~> 1.7.0)
@@ -1118,6 +1125,7 @@ DEPENDENCIES
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphql (~> 1.8.0)
+ graphql-docs (~> 1.6.0)
grpc (~> 1.19.0)
haml_lint (~> 0.31.0)
hamlit (~> 2.8.8)
@@ -1149,10 +1157,9 @@ 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.0)
+ net-ssh (~> 5.2)
nokogiri (~> 1.10.3)
oauth2 (~> 1.4)
octokit (~> 4.9)
@@ -1177,10 +1184,8 @@ DEPENDENCIES
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)
@@ -1194,7 +1199,7 @@ DEPENDENCIES
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
rack-timeout
- rails (= 5.1.7)
+ rails (= 5.2.3)
rails-controller-testing
rails-i18n (~> 5.1)
rainbow (~> 3.0)
@@ -1212,7 +1217,7 @@ DEPENDENCIES
rouge (~> 3.5)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
- rspec-rails (~> 3.7.0)
+ rspec-rails (~> 3.8.0)
rspec-retry (~> 0.6.1)
rspec-set (~> 0.1.3)
rspec_junit_formatter
diff --git a/README.md b/README.md
index e64f04cea59..51dc87ac653 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ To see how GitLab looks please see the [features page on our website](https://ab
- Manage Git repositories with fine grained access controls that keep your code secure
- Perform code reviews and enhance collaboration with merge requests
-- Complete continuous integration (CI) and CD pipelines to builds, test, and deploy your applications
+- Complete continuous integration (CI) and continuous deployment/delivery (CD) pipelines to build, test, and deploy your applications
- Each project can also have an issue tracker, issue board, and a wiki
- 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)
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/api.js b/app/assets/javascripts/api.js
index 4f66a5d080c..a649c521405 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -24,6 +24,7 @@ const Api = {
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
+ userCountsPath: '/api/:version/user_counts',
usersPath: '/api/:version/users.json',
userPath: '/api/:version/users/:id',
userStatusPath: '/api/:version/users/:id/status',
@@ -312,6 +313,11 @@ const Api = {
});
},
+ userCounts() {
+ const url = Api.buildUrl(this.userCountsPath);
+ return axios.get(url);
+ },
+
userStatus(id, options) {
const url = Api.buildUrl(this.userStatusPath).replace(':id', encodeURIComponent(id));
return axios.get(url, {
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index bfb073fdcdc..789a057caf8 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,9 @@ $.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());
+ if (gon.features && gon.features.gfmEmbeddedMetrics) {
+ 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 b23de36f860..27708504791 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -33,10 +33,12 @@ export default function renderMermaid($els) {
flowchart: {
htmlLabels: false,
},
+ securityLevel: 'strict',
});
$els.each((i, el) => {
- const source = el.textContent;
+ // Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
+ const source = el.textContent.replace(/<br\s*\/>/g, '<br>');
/**
* Restrict the rendering to a certain amount of character to
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/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..6754abf4019
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -0,0 +1,216 @@
+<script>
+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/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/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index a1d634c8f19..5f100c617a0 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -1,4 +1,5 @@
<script>
+import footerEEMixin from 'ee_else_ce/boards/mixins/modal_footer';
import Flash from '../../../flash';
import { __, n__ } from '../../../locale';
import ListsDropdown from './lists_dropdown.vue';
@@ -10,7 +11,7 @@ export default {
components: {
ListsDropdown,
},
- mixins: [modalMixin],
+ mixins: [modalMixin, footerEEMixin],
data() {
return {
modal: ModalStore.store,
@@ -41,7 +42,7 @@ export default {
const req = this.buildUpdateRequest(list);
// Post the data to the backend
- gl.boardService.bulkUpdate(issueIds, req).catch(() => {
+ boardsStore.bulkUpdate(issueIds, req).catch(() => {
Flash(__('Failed to update issues, please try again.'));
selectedIssues.forEach(issue => {
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/config_toggle.js b/app/assets/javascripts/boards/config_toggle.js
new file mode 100644
index 00000000000..2d1ec238274
--- /dev/null
+++ b/app/assets/javascripts/boards/config_toggle.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/app/assets/javascripts/boards/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 23b107abefa..3bded4a3258 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,34 +1,43 @@
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';
import './models/assignee';
-import FilteredSearchBoards from './filtered_search_boards';
-import eventHub from './eventhub';
+import FilteredSearchBoards from '~/boards/filtered_search_boards';
+import eventHub from '~/boards/eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
-import './models/issue';
-import './models/list';
-import './models/milestone';
-import './models/project';
-import boardsStore from './stores/boards_store';
-import ModalStore from './stores/modal_store';
-import BoardService from './services/board_service';
-import modalMixin from './mixins/modal_mixins';
-import './filters/due_date_filters';
-import Board from './components/board';
-import BoardSidebar from './components/board_sidebar';
+import 'ee_else_ce/boards/models/issue';
+import 'ee_else_ce/boards/models/list';
+import '~/boards/models/milestone';
+import '~/boards/models/project';
+import boardsStore from '~/boards/stores/boards_store';
+import ModalStore from '~/boards/stores/modal_store';
+import BoardService from 'ee_else_ce/boards/services/board_service';
+import modalMixin from '~/boards/mixins/modal_mixins';
+import '~/boards/filters/due_date_filters';
+import Board from 'ee_else_ce/boards/components/board';
+import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar';
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
-import BoardAddIssuesModal from './components/modal/index.vue';
+import BoardAddIssuesModal from '~/boards/components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
import {
NavigationType,
convertObjectPropsToCamelCase,
parseBoolean,
} from '~/lib/utils/common_utils';
+import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
+import toggleFocusMode from 'ee_else_ce/boards/toggle_focus';
+import {
+ setPromotionState,
+ setWeigthFetchingState,
+ setEpicFetchingState,
+ getMilestoneTitle,
+ getBoardsModalData,
+} from 'ee_else_ce/boards/ee_functions';
+import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
let issueBoardsApp;
@@ -127,6 +136,7 @@ export default () => {
});
boardsStore.addBlankState();
+ setPromotionState(boardsStore);
this.loading = false;
})
.catch(() => {
@@ -141,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 => {
@@ -155,6 +167,8 @@ export default () => {
} = convertObjectPropsToCamelCase(data);
newIssue.setFetchingState('subscriptions', false);
+ setWeigthFetchingState(newIssue, false);
+ setEpicFetchingState(newIssue, false);
newIssue.updateData({
humanTimeSpent: humanTotalTimeSpent,
timeSpent: totalTimeSpent,
@@ -167,6 +181,7 @@ export default () => {
})
.catch(() => {
newIssue.setFetchingState('subscriptions', false);
+ setWeigthFetchingState(newIssue, false);
Flash(__('An error occurred while fetching sidebar data'));
});
}
@@ -201,12 +216,15 @@ export default () => {
el: document.getElementById('js-add-list'),
data: {
filters: boardsStore.state.filters,
+ ...getMilestoneTitle($boardApp),
},
mounted() {
initNewListDropdown();
},
});
+ boardConfigToggle(boardsStore);
+
const issueBoardsModal = document.getElementById('js-add-issues-btn');
if (issueBoardsModal) {
@@ -218,6 +236,7 @@ export default () => {
return {
modal: ModalStore.store,
store: boardsStore.state,
+ ...getBoardsModalData($boardApp),
canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
};
},
@@ -281,5 +300,6 @@ export default () => {
});
}
+ toggleFocusMode(ModalStore, boardsStore, $boardApp);
mountMultipleBoardsSwitcher();
};
diff --git a/app/assets/javascripts/boards/mixins/modal_footer.js b/app/assets/javascripts/boards/mixins/modal_footer.js
new file mode 100644
index 00000000000..ff8b4c56321
--- /dev/null
+++ b/app/assets/javascripts/boards/mixins/modal_footer.js
@@ -0,0 +1 @@
+export default {};
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 580d04a3649..5202620057c 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -62,6 +62,22 @@ export default class BoardService {
static toggleIssueSubscription(endpoint) {
return boardsStore.toggleIssueSubscription(endpoint);
}
+
+ allBoards() {
+ return boardsStore.allBoards();
+ }
+
+ recentBoards() {
+ return boardsStore.recentBoards();
+ }
+
+ createBoard(board) {
+ return boardsStore.createBoard(board);
+ }
+
+ deleteBoard({ id }) {
+ return boardsStore.deleteBoard({ id });
+ }
}
window.BoardService = BoardService;
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index b9cd4a143ef..f57c684691c 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -340,6 +340,44 @@ const boardsStore = {
toggleIssueSubscription(endpoint) {
return axios.post(endpoint);
},
+
+ allBoards() {
+ return axios.get(this.generateBoardsPath());
+ },
+
+ recentBoards() {
+ return axios.get(this.state.endpoints.recentBoardsEndpoint);
+ },
+
+ createBoard(board) {
+ const boardPayload = { ...board };
+ boardPayload.label_ids = (board.labels || []).map(b => b.id);
+
+ if (boardPayload.label_ids.length === 0) {
+ boardPayload.label_ids = [''];
+ }
+
+ if (boardPayload.assignee) {
+ boardPayload.assignee_id = boardPayload.assignee.id;
+ }
+
+ if (boardPayload.milestone) {
+ boardPayload.milestone_id = boardPayload.milestone.id;
+ }
+
+ if (boardPayload.id) {
+ return axios.put(this.generateBoardsPath(boardPayload.id), { board: boardPayload });
+ }
+ return axios.post(this.generateBoardsPath(), { board: boardPayload });
+ },
+
+ deleteBoard({ id }) {
+ return axios.delete(this.generateBoardsPath(id));
+ },
+
+ setCurrentBoard(board) {
+ this.state.currentBoard = board;
+ },
};
BoardsStoreEE.initEESpecific(boardsStore);
diff --git a/app/assets/javascripts/boards/toggle_focus.js b/app/assets/javascripts/boards/toggle_focus.js
new file mode 100644
index 00000000000..2d1ec238274
--- /dev/null
+++ b/app/assets/javascripts/boards/toggle_focus.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
index 0d2fe2925d8..ad0f6cc1496 100644
--- a/app/assets/javascripts/commons/index.js
+++ b/app/assets/javascripts/commons/index.js
@@ -4,3 +4,6 @@ import './jquery';
import './bootstrap';
import './vue';
import '../lib/utils/axios_utils';
+import { openUserCountsBroadcast } from './nav/user_merge_requests';
+
+openUserCountsBroadcast();
diff --git a/app/assets/javascripts/commons/nav/user_merge_requests.js b/app/assets/javascripts/commons/nav/user_merge_requests.js
new file mode 100644
index 00000000000..8e694cca6a1
--- /dev/null
+++ b/app/assets/javascripts/commons/nav/user_merge_requests.js
@@ -0,0 +1,67 @@
+import Api from '~/api';
+
+let channel;
+
+function broadcastCount(newCount) {
+ if (!channel) {
+ return;
+ }
+
+ channel.postMessage(newCount);
+}
+
+function updateUserMergeRequestCounts(newCount) {
+ const mergeRequestsCountEl = document.querySelector('.merge-requests-count');
+ mergeRequestsCountEl.textContent = newCount.toLocaleString();
+ mergeRequestsCountEl.classList.toggle('hidden', Number(newCount) === 0);
+}
+
+/**
+ * Refresh user counts (and broadcast if open)
+ */
+export function refreshUserMergeRequestCounts() {
+ return Api.userCounts()
+ .then(({ data }) => {
+ const count = data.merge_requests;
+
+ updateUserMergeRequestCounts(count);
+ broadcastCount(count);
+ })
+ .catch(ex => {
+ console.error(ex); // eslint-disable-line no-console
+ });
+}
+
+/**
+ * Close the broadcast channel for user counts
+ */
+export function closeUserCountsBroadcast() {
+ if (!channel) {
+ return;
+ }
+
+ channel.close();
+ channel = null;
+}
+
+/**
+ * Open the broadcast channel for user counts, adds user id so we only update
+ *
+ * **Please note:**
+ * Not supported in all browsers, but not polyfilling for now
+ * to keep bundle size small and
+ * no special functionality lost except cross tab notifications
+ */
+export function openUserCountsBroadcast() {
+ closeUserCountsBroadcast();
+
+ if (window.BroadcastChannel) {
+ const currentUserId = typeof gon !== 'undefined' && gon && gon.current_user_id;
+ if (currentUserId) {
+ channel = new BroadcastChannel(`mr_count_channel_${currentUserId}`);
+ channel.onmessage = ev => {
+ updateUserMergeRequestCounts(ev.data);
+ };
+ }
+ }
+}
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 07e83c329ac..daa941a63cd 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -14,6 +14,7 @@ import 'core-js/es/string/from-code-point';
import 'core-js/es/string/includes';
import 'core-js/es/string/repeat';
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/confidential_merge_request/components/project_form_group.vue b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
index b89729375be..197a0706062 100644
--- a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
+++ b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
@@ -41,7 +41,7 @@ export default {
noForkText() {
return sprintf(
__(
- 'To protect this issues confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.',
+ "To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.",
),
{ link_start: `<a href="${this.newForkPath}" class="help-link">`, link_end: '</a>' },
false,
@@ -105,7 +105,7 @@ export default {
</script>
<template>
- <div class="form-group">
+ <div class="confidential-merge-request-fork-group form-group">
<label>{{ __('Project') }}</label>
<div>
<dropdown
@@ -118,7 +118,7 @@ export default {
<template v-if="projects.length">
{{
__(
- 'To protect this issues confidentiality, a private fork of this project was selected.',
+ "To protect this issue's confidentiality, a private fork of this project was selected.",
)
}}
</template>
@@ -126,7 +126,11 @@ export default {
{{ __('No forks available to you.') }}<br />
<span v-html="noForkText"></span>
</template>
- <gl-link :href="helpPagePath" class="help-link" target="_blank">
+ <gl-link
+ :href="helpPagePath"
+ class="w-auto p-0 d-inline-block text-primary bg-transparent"
+ target="_blank"
+ >
<span class="sr-only">{{ __('Read more') }}</span>
<i class="fa fa-question-circle" aria-hidden="true"></i>
</gl-link>
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js
index 052168bb21c..dce9c1a5410 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/create_merge_request_dropdown.js
@@ -182,7 +182,7 @@ export default class CreateMergeRequestDropdown {
}
enable() {
- if (!canCreateConfidentialMergeRequest()) return;
+ if (isConfidentialIssue() && !canCreateConfidentialMergeRequest()) return;
this.createMergeRequestButton.classList.remove('disabled');
this.createMergeRequestButton.removeAttribute('disabled');
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index b56e08175cc..d4b994d4922 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -17,6 +17,7 @@ Vue.use(Translate);
export default () => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
+ const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
// eslint-disable-next-line no-new
new Vue({
@@ -33,7 +34,6 @@ export default () => {
'stage-production-component': stageComponent,
},
data() {
- const cycleAnalyticsEl = document.querySelector('#cycle-analytics');
const cycleAnalyticsService = new CycleAnalyticsService({
requestPath: cycleAnalyticsEl.dataset.requestPath,
});
@@ -56,7 +56,13 @@ export default () => {
},
},
created() {
- this.fetchCycleAnalyticsData();
+ // Conditional check placed here to prevent this method from being called on the
+ // new Cycle Analytics page (i.e. the new page will be initialized blank and only
+ // after a group is selected the cycle analyitcs data will be fetched). Once the
+ // old (current) page has been removed this entire created method as well as the
+ // variable itself can be completely removed.
+ // Follow up issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/64490
+ if (cycleAnalyticsEl.dataset.requestPath) this.fetchCycleAnalyticsData();
},
methods: {
handleError() {
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 63350fafefa..2514274224d 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -67,6 +67,18 @@ export default {
errorMessage() {
return this.file.viewer.error_message;
},
+ forkMessage() {
+ return sprintf(
+ __(
+ "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.",
+ ),
+ {
+ tag_start: '<span class="js-file-fork-suggestion-section-action">',
+ tag_end: '</span>',
+ },
+ false,
+ );
+ },
},
watch: {
isCollapsed: function fileCollapsedWatch(newVal, oldVal) {
@@ -150,12 +162,7 @@ export default {
/>
<div v-if="forkMessageVisible" class="js-file-fork-suggestion-section file-fork-suggestion">
- <span class="file-fork-suggestion-note">
- {{ sprintf(__("You're not allowed to %{tag_start}edit%{tag_end} files in this project
- directly. Please fork this project, make your changes there, and submit a merge request."),
- { tag_start: '<span class="js-file-fork-suggestion-section-action">', tag_end: '</span>' })
- }}
- </span>
+ <span class="file-fork-suggestion-note" v-html="forkMessage"></span>
<a
:href="file.fork_path"
class="js-fork-suggestion-button btn btn-grouped btn-inverted btn-success"
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index 64b09c8b62c..77080691dcb 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -17,11 +17,13 @@ export default class FilterableList {
}
getFilterEndpoint() {
- return `${this.filterForm.getAttribute('action')}?${$(this.filterForm).serialize()}`;
+ return this.getPagePath();
}
getPagePath() {
- return this.getFilterEndpoint();
+ const action = this.filterForm.getAttribute('action');
+ const params = $(this.filterForm).serialize();
+ return `${action}${action.indexOf('?') > 0 ? '&' : '?'}${params}`;
}
initSearch() {
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/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..04e86afb268 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,
@@ -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_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index bc9d7fcf30d..c855f3973b0 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -1,4 +1,4 @@
-/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback, no-unused-vars */
+/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback */
import $ from 'jquery';
import _ from 'underscore';
@@ -7,7 +7,7 @@ import Flash from './flash';
import { __ } from './locale';
export default {
- init({ container, form, issues, prefixId } = {}) {
+ init({ form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_';
this.form = form || this.getElement('.bulk-update');
this.$labelDropdown = this.form.find('.js-label-select');
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index 16f88cddce3..f3f8b6ec715 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -2,26 +2,13 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { s__, __ } from './locale';
-import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
-import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
+import issuableInitBulkUpdateSidebar from './issuable_init_bulk_update_sidebar';
export default class IssuableIndex {
constructor(pagePrefix) {
- this.initBulkUpdate(pagePrefix);
+ issuableInitBulkUpdateSidebar.init(pagePrefix);
IssuableIndex.resetIncomingEmailToken();
}
- initBulkUpdate(pagePrefix) {
- const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
- const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
-
- if (userCanBulkUpdate && !alreadyInitialized) {
- IssuableBulkUpdateActions.init({
- prefixId: pagePrefix,
- });
-
- this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
- }
- }
static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset');
diff --git a/app/assets/javascripts/issuable_init_bulk_update_sidebar.js b/app/assets/javascripts/issuable_init_bulk_update_sidebar.js
new file mode 100644
index 00000000000..da8969c80f3
--- /dev/null
+++ b/app/assets/javascripts/issuable_init_bulk_update_sidebar.js
@@ -0,0 +1,19 @@
+import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
+import issuableBulkUpdateActions from './issuable_bulk_update_actions';
+
+export default {
+ bulkUpdateSidebar: null,
+
+ init(prefixId) {
+ const bulkUpdateEl = document.querySelector('.issues-bulk-update');
+ const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
+
+ if (bulkUpdateEl && !alreadyInitialized) {
+ issuableBulkUpdateActions.init({ prefixId });
+
+ this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
+ }
+
+ return this.bulkUpdateSidebar;
+ },
+};
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index de2a9664cde..9ca38d6bbfa 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -55,6 +55,11 @@ export default {
required: false,
default: true,
},
+ zoomMeetingUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
issuableRef: {
type: String,
required: true,
@@ -342,7 +347,7 @@ export default {
:title-text="state.titleText"
:show-inline-edit-button="showInlineEditButton"
/>
- <pinned-links :description-html="state.descriptionHtml" />
+ <pinned-links :zoom-meeting-url="zoomMeetingUrl" />
<description-component
v-if="state.descriptionHtml"
:can-update="canUpdate"
diff --git a/app/assets/javascripts/issue_show/components/locked_warning.vue b/app/assets/javascripts/issue_show/components/locked_warning.vue
index 2f3e611e089..19c7a11d87b 100644
--- a/app/assets/javascripts/issue_show/components/locked_warning.vue
+++ b/app/assets/javascripts/issue_show/components/locked_warning.vue
@@ -1,18 +1,27 @@
<script>
+import { __, sprintf } from '~/locale';
+
export default {
computed: {
currentPath() {
return window.location.pathname;
},
+ alertMessage() {
+ return sprintf(
+ __(
+ 'Someone edited the issue at the same time you did. Please check out %{linkStart}the issue%{linkEnd} and make sure your changes will not unintentionally remove theirs.',
+ ),
+ {
+ linkStart: `<a href="${this.currentPath}" target="_blank" rel="nofollow">`,
+ linkEnd: `</a>`,
+ },
+ false,
+ );
+ },
},
};
</script>
<template>
- <div class="alert alert-danger">
- {{ sprintf(__("Someone edited the issue at the same time you did. Please check out
- %{linkStart}%the issue%{linkEnd} and make sure your changes will not unintentionally remove
- theirs."), { linkStart: `<a href="${currentPath}" target="_blank" rel="nofollow">` linkEnd: '</a
- >', }) }}
- </div>
+ <div class="alert alert-danger" v-html="alertMessage"></div>
</template>
diff --git a/app/assets/javascripts/issue_show/components/pinned_links.vue b/app/assets/javascripts/issue_show/components/pinned_links.vue
index 7a54b26bc2b..965e8a3d751 100644
--- a/app/assets/javascripts/issue_show/components/pinned_links.vue
+++ b/app/assets/javascripts/issue_show/components/pinned_links.vue
@@ -8,40 +8,19 @@ export default {
GlLink,
},
props: {
- descriptionHtml: {
+ zoomMeetingUrl: {
type: String,
- required: true,
- },
- },
- computed: {
- linksInDescription() {
- const el = document.createElement('div');
- el.innerHTML = this.descriptionHtml;
- return [...el.querySelectorAll('a')].map(a => a.href);
- },
- // Detect links matching the following formats:
- // Zoom Start links: https://zoom.us/s/<meeting-id>
- // Zoom Join links: https://zoom.us/j/<meeting-id>
- // Personal Zoom links: https://zoom.us/my/<meeting-id>
- // Vanity Zoom links: https://gitlab.zoom.us/j/<meeting-id> (also /s and /my)
- zoomHref() {
- const zoomRegex = /^https:\/\/([\w\d-]+\.)?zoom\.us\/(s|j|my)\/.+/;
- return this.linksInDescription.reduce((acc, currentLink) => {
- let lastLink = acc;
- if (zoomRegex.test(currentLink)) {
- lastLink = currentLink;
- }
- return lastLink;
- }, '');
+ required: false,
+ default: null,
},
},
};
</script>
<template>
- <div v-if="zoomHref" class="border-bottom mb-3 mt-n2">
+ <div v-if="zoomMeetingUrl" class="border-bottom mb-3 mt-n2">
<gl-link
- :href="zoomHref"
+ :href="zoomMeetingUrl"
target="_blank"
class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
>
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index bea43430edc..f50a6e3b19d 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -311,7 +311,8 @@ export default class LabelsSelect {
// We need to identify which items are actually labels
if (label.id) {
- selectedClass.push('label-item');
+ const selectedLayoutClasses = ['d-flex', 'flex-row', 'text-break-word'];
+ selectedClass.push('label-item', ...selectedLayoutClasses);
linkEl.dataset.labelId = label.id;
}
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 81773bd140e..edf9423c74c 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -8,6 +8,7 @@ import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue';
import { chartHeight, graphTypes, lineTypes } from '../../constants';
import { makeDataSeries } from '~/helpers/monitor_helper';
+import { graphDataValidatorForValues } from '../../utils';
let debouncedResize;
@@ -23,19 +24,7 @@ export default {
graphData: {
type: Object,
required: true,
- validator(data) {
- return (
- Array.isArray(data.queries) &&
- data.queries.filter(query => {
- if (Array.isArray(query.result)) {
- return (
- query.result.filter(res => Array.isArray(res.values)).length === query.result.length
- );
- }
- return false;
- }).length === data.queries.length
- );
- },
+ validator: graphDataValidatorForValues.bind(null, false),
},
containerWidth: {
type: Number,
@@ -48,7 +37,13 @@ export default {
},
projectPath: {
type: String,
- required: true,
+ required: false,
+ default: () => '',
+ },
+ showBorder: {
+ type: Boolean,
+ required: false,
+ default: () => false,
},
thresholds: {
type: Array,
@@ -245,52 +240,54 @@ 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="col-12 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
+ <div class="prometheus-graph" :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/column.vue b/app/assets/javascripts/monitoring/components/charts/column.vue
index 05a2036f4c3..83136d43479 100644
--- a/app/assets/javascripts/monitoring/components/charts/column.vue
+++ b/app/assets/javascripts/monitoring/components/charts/column.vue
@@ -4,6 +4,7 @@ import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import { chartHeight } from '../../constants';
import { makeDataSeries } from '~/helpers/monitor_helper';
+import { graphDataValidatorForValues } from '../../utils';
export default {
components: {
@@ -14,23 +15,11 @@ export default {
graphData: {
type: Object,
required: true,
- validator(data) {
- return (
- Array.isArray(data.queries) &&
- data.queries.filter(query => {
- if (Array.isArray(query.result)) {
- return (
- query.result.filter(res => Array.isArray(res.values)).length === query.result.length
- );
- }
- return false;
- }).length === data.queries.length
- );
- },
- containerWidth: {
- type: Number,
- required: true,
- },
+ validator: graphDataValidatorForValues.bind(null, false),
+ },
+ containerWidth: {
+ type: Number,
+ required: true,
},
},
data() {
diff --git a/app/assets/javascripts/monitoring/components/charts/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/single_stat.vue b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
index b03a6ca1806..7428b27a9c3 100644
--- a/app/assets/javascripts/monitoring/components/charts/single_stat.vue
+++ b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
@@ -1,5 +1,7 @@
<script>
import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { roundOffFloat } from '~/lib/utils/common_utils';
+import { graphDataValidatorForValues } from '../../utils';
export default {
components: {
@@ -7,22 +9,21 @@ export default {
},
inheritAttrs: false,
props: {
- title: {
- type: String,
- required: true,
- },
- value: {
- type: Number,
- required: true,
- },
- unit: {
- type: String,
+ graphData: {
+ type: Object,
required: true,
+ validator: graphDataValidatorForValues.bind(null, true),
},
},
computed: {
- valueWithUnit() {
- return `${this.value}${this.unit}`;
+ queryInfo() {
+ return this.graphData.queries[0];
+ },
+ engineeringNotation() {
+ return `${roundOffFloat(this.queryInfo.result[0].value[1], 1)}${this.queryInfo.unit}`;
+ },
+ graphTitle() {
+ return this.queryInfo.label;
},
},
};
@@ -30,8 +31,8 @@ export default {
<template>
<div class="prometheus-graph col-12 col-lg-6">
<div class="prometheus-graph-header">
- <h5 ref="graphTitle" class="prometheus-graph-title">{{ title }}</h5>
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
- <gl-single-stat :value="valueWithUnit" :title="title" variant="success" />
+ <gl-single-stat :value="engineeringNotation" :title="graphTitle" variant="success" />
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index ba79a697df2..745488255ab 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -7,17 +7,20 @@ import Icon from '~/vue_shared/components/icon.vue';
import { getParameterValues } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import MonitorAreaChart from './charts/area.vue';
+import MonitorSingleStatChart from './charts/single_stat.vue';
+import PanelType from './panel_type.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
-import { timeWindows, timeWindowsKeyNames } from '../constants';
+import { sidebarAnimationDuration, timeWindows, timeWindowsKeyNames } from '../constants';
import { getTimeDiff } from '../utils';
-const sidebarAnimationDuration = 150;
let sidebarMutationObserver;
export default {
components: {
MonitorAreaChart,
+ MonitorSingleStatChart,
+ PanelType,
GraphGroup,
EmptyState,
Icon,
@@ -152,10 +155,8 @@ export default {
'useDashboardEndpoint',
'allDashboards',
'multipleDashboardsEnabled',
+ 'additionalPanelTypesEnabled',
]),
- groupsWithData() {
- return this.groups.filter(group => this.chartsWithData(group.metrics).length > 0);
- },
selectedDashboardText() {
return this.currentDashboard || (this.allDashboards[0] && this.allDashboards[0].display_name);
},
@@ -173,6 +174,7 @@ export default {
deploymentsEndpoint: this.deploymentsEndpoint,
dashboardEndpoint: this.dashboardEndpoint,
currentDashboard: this.currentDashboard,
+ projectPath: this.projectPath,
});
this.timeWindows = timeWindows;
@@ -220,6 +222,8 @@ export default {
chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
);
},
+ // TODO: BEGIN, Duplicated code with panel_type until feature flag is removed
+ // Issue number: https://gitlab.com/gitlab-org/gitlab-ce/issues/63845
getGraphAlerts(queries) {
if (!this.allAlerts) return {};
const metricIdsForChart = queries.map(q => q.metricId);
@@ -228,6 +232,7 @@ export default {
getGraphAlertValues(queries) {
return Object.values(this.getGraphAlerts(queries));
},
+ // TODO: END
hideAddMetricModal() {
this.$refs.addMetricModal.hide();
},
@@ -248,6 +253,9 @@ export default {
setTimeWindowParameter(key) {
return `?time_window=${key}`;
},
+ groupHasData(group) {
+ return this.chartsWithData(group.metrics).length > 0;
+ },
},
addMetric: {
title: s__('Metrics|Add metric'),
@@ -361,29 +369,40 @@ export default {
</div>
<div v-if="!showEmptyState">
<graph-group
- v-for="(groupData, index) in groupsWithData"
- :key="index"
+ v-for="groupData in groups"
+ :key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group"
:show-panels="showPanels"
+ :collapse-group="groupHasData(groupData)"
>
- <monitor-area-chart
- v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
- :key="graphIndex"
- :project-path="projectPath"
- :graph-data="graphData"
- :deployment-data="deploymentData"
- :thresholds="getGraphAlertValues(graphData.queries)"
- :container-width="elWidth"
- group-id="monitor-area-chart"
- >
- <alert-widget
- v-if="alertWidgetAvailable && graphData"
- :alerts-endpoint="alertsEndpoint"
- :relevant-queries="graphData.queries"
- :alerts-to-manage="getGraphAlerts(graphData.queries)"
- @setAlerts="setAlerts"
+ <template v-if="additionalPanelTypesEnabled">
+ <panel-type
+ v-for="(graphData, graphIndex) in groupData.metrics"
+ :key="`panel-type-${graphIndex}`"
+ :graph-data="graphData"
+ :dashboard-width="elWidth"
/>
- </monitor-area-chart>
+ </template>
+ <template v-else>
+ <monitor-area-chart
+ v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
+ :key="graphIndex"
+ :graph-data="graphData"
+ :deployment-data="deploymentData"
+ :thresholds="getGraphAlertValues(graphData.queries)"
+ :container-width="elWidth"
+ :project-path="projectPath"
+ group-id="monitor-area-chart"
+ >
+ <alert-widget
+ v-if="alertWidgetAvailable && graphData"
+ :alerts-endpoint="alertsEndpoint"
+ :relevant-queries="graphData.queries"
+ :alerts-to-manage="getGraphAlerts(graphData.queries)"
+ @setAlerts="setAlerts"
+ />
+ </monitor-area-chart>
+ </template>
</graph-group>
</div>
<empty-state
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
new file mode 100644
index 00000000000..e17f03de0fd
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -0,0 +1,97 @@
+<script>
+import { mapActions, mapState } from 'vuex';
+import GraphGroup from './graph_group.vue';
+import MonitorAreaChart from './charts/area.vue';
+import { sidebarAnimationDuration, timeWindowsKeyNames, timeWindows } from '../constants';
+import { getTimeDiff } from '../utils';
+
+let sidebarMutationObserver;
+
+export default {
+ components: {
+ GraphGroup,
+ MonitorAreaChart,
+ },
+ props: {
+ dashboardUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ params: {
+ ...getTimeDiff(timeWindows[timeWindowsKeyNames.eightHours]),
+ },
+ elWidth: 0,
+ };
+ },
+ computed: {
+ ...mapState('monitoringDashboard', ['groups', 'metricsWithData']),
+ groupData() {
+ const groupsWithData = this.groups.filter(group => this.chartsWithData(group.metrics).length);
+ if (groupsWithData.length) {
+ return groupsWithData[0];
+ }
+ return null;
+ },
+ },
+ 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',
+ ]),
+ chartsWithData(charts) {
+ return charts.filter(chart =>
+ 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: this.dashboardUrl,
+ });
+ this.setShowErrorBanner(false);
+ },
+ },
+};
+</script>
+<template>
+ <div class="metrics-embed">
+ <div v-if="groupData" class="row w-100 m-n2 pb-4">
+ <monitor-area-chart
+ v-for="graphData in chartsWithData(groupData.metrics)"
+ :key="graphData.title"
+ :graph-data="graphData"
+ :container-width="elWidth"
+ group-id="monitor-area-chart"
+ :project-path="null"
+ :show-border="true"
+ />
+ </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
new file mode 100644
index 00000000000..d7cd2c57871
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -0,0 +1,71 @@
+<script>
+import { mapState } from 'vuex';
+import _ from 'underscore';
+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,
+ },
+ props: {
+ graphData: {
+ type: Object,
+ required: true,
+ },
+ dashboardWidth: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState('monitoringDashboard', ['deploymentData', 'projectPath']),
+ alertWidgetAvailable() {
+ return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
+ },
+ graphDataHasMetrics() {
+ return this.graphData.queries[0].result.length > 0;
+ },
+ },
+ methods: {
+ getGraphAlerts(queries) {
+ if (!this.allAlerts) return {};
+ const metricIdsForChart = queries.map(q => q.metricId);
+ return _.pick(this.allAlerts, alert => metricIdsForChart.includes(alert.metricId));
+ },
+ getGraphAlertValues(queries) {
+ return Object.values(this.getGraphAlerts(queries));
+ },
+ isPanelType(type) {
+ return this.graphData.type && this.graphData.type === type;
+ },
+ },
+};
+</script>
+<template>
+ <monitor-single-stat-chart
+ v-if="isPanelType('single-stat') && graphDataHasMetrics"
+ :graph-data="graphData"
+ />
+ <monitor-area-chart
+ v-else-if="graphDataHasMetrics"
+ :graph-data="graphData"
+ :deployment-data="deploymentData"
+ :project-path="projectPath"
+ :thresholds="getGraphAlertValues(graphData.queries)"
+ :container-width="dashboardWidth"
+ group-id="monitor-area-chart"
+ >
+ <alert-widget
+ v-if="alertWidgetAvailable"
+ :alerts-endpoint="alertsEndpoint"
+ :relevant-queries="graphData.queries"
+ :alerts-to-manage="getGraphAlerts(graphData.queries)"
+ @setAlerts="setAlerts"
+ />
+ </monitor-area-chart>
+ <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..605c95e6da5 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -1,5 +1,7 @@
import { __ } from '~/locale';
+export const sidebarAnimationDuration = 300; // milliseconds.
+
export const chartHeight = 300;
export const graphTypes = {
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 97d149e9ad5..c0fee1ebb99 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -12,6 +12,7 @@ export default (props = {}) => {
store.dispatch('monitoringDashboard/setFeatureFlags', {
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
+ additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
});
}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 0fa2a5d6370..245cc2eaca3 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -37,10 +37,15 @@ export const setEndpoints = ({ commit }, endpoints) => {
export const setFeatureFlags = (
{ commit },
- { prometheusEndpointEnabled, multipleDashboardsEnabled },
+ { prometheusEndpointEnabled, multipleDashboardsEnabled, additionalPanelTypesEnabled },
) => {
commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
commit(types.SET_MULTIPLE_DASHBOARDS_ENABLED, multipleDashboardsEnabled);
+ commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
+};
+
+export const setShowErrorBanner = ({ commit }, enabled) => {
+ commit(types.SET_SHOW_ERROR_BANNER, enabled);
};
export const requestMetricsDashboard = ({ commit }) => {
@@ -98,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'));
+ }
});
};
@@ -118,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'));
+ }
});
};
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 2c78a0b9315..4b1aadbcf05 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -11,7 +11,9 @@ export const SET_QUERY_RESULT = 'SET_QUERY_RESULT';
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
export const SET_DASHBOARD_ENABLED = 'SET_DASHBOARD_ENABLED';
export const SET_MULTIPLE_DASHBOARDS_ENABLED = 'SET_MULTIPLE_DASHBOARDS_ENABLED';
+export const SET_ADDITIONAL_PANEL_TYPES_ENABLED = 'SET_ADDITIONAL_PANEL_TYPES_ENABLED';
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
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 a85a7723c1f..b19520d6638 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -75,6 +75,7 @@ export default {
state.deploymentsEndpoint = endpoints.deploymentsEndpoint;
state.dashboardEndpoint = endpoints.dashboardEndpoint;
state.currentDashboard = endpoints.currentDashboard;
+ state.projectPath = endpoints.projectPath;
},
[types.SET_DASHBOARD_ENABLED](state, enabled) {
state.useDashboardEndpoint = enabled;
@@ -92,4 +93,10 @@ export default {
[types.SET_ALL_DASHBOARDS](state, dashboards) {
state.allDashboards = dashboards;
},
+ [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 de711d6ccae..440bdc951e0 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -9,12 +9,15 @@ export default () => ({
dashboardEndpoint: invalidUrl,
useDashboardEndpoint: false,
multipleDashboardsEnabled: false,
+ additionalPanelTypesEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,
+ showErrorBanner: true,
groups: [],
deploymentData: [],
environments: [],
metricsWithData: [],
allDashboards: [],
currentDashboard: null,
+ projectPath: null,
});
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index 721942f9d3b..938ee2f0a9a 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -69,13 +69,26 @@ export const sortMetrics = metrics =>
.sortBy('weight')
.value();
-export const normalizeQueryResult = timeSeries => ({
- ...timeSeries,
- values: timeSeries.values.map(([timestamp, value]) => [
- new Date(timestamp * 1000).toISOString(),
- Number(value),
- ]),
-});
+export const normalizeQueryResult = timeSeries => {
+ let normalizedResult = {};
+
+ if (timeSeries.values) {
+ normalizedResult = {
+ ...timeSeries,
+ values: timeSeries.values.map(([timestamp, value]) => [
+ new Date(timestamp * 1000).toISOString(),
+ Number(value),
+ ]),
+ };
+ } else if (timeSeries.value) {
+ normalizedResult = {
+ ...timeSeries,
+ value: [new Date(timeSeries.value[0] * 1000).toISOString(), Number(timeSeries.value[1])],
+ };
+ }
+
+ return normalizedResult;
+};
export const normalizeMetrics = metrics => {
const groupedMetrics = groupQueriesByChartInfo(metrics);
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index ef309c8a398..478e2b3d06c 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -30,4 +30,28 @@ export const getTimeDiff = selectedTimeWindow => {
return { start, end };
};
+/**
+ * This method is used to validate if the graph data format for a chart component
+ * that needs a time series as a response from a prometheus query (query_range) is
+ * of a valid format or not.
+ * @param {Object} graphData the graph data response from a prometheus request
+ * @returns {boolean} whether the graphData format is correct
+ */
+export const graphDataValidatorForValues = (isValues, graphData) => {
+ const responseValueKeyName = isValues ? 'value' : 'values';
+
+ return (
+ Array.isArray(graphData.queries) &&
+ graphData.queries.filter(query => {
+ if (Array.isArray(query.result)) {
+ return (
+ query.result.filter(res => Array.isArray(res[responseValueKeyName])).length ===
+ query.result.length
+ );
+ }
+ return false;
+ }).length === graphData.queries.length
+ );
+};
+
export default {};
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 6c1738f0f1b..fda494fec07 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -13,6 +13,7 @@ import {
splitCamelCase,
slugifyWithUnderscore,
} from '../../lib/utils/text_utility';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
@@ -234,7 +235,10 @@ export default {
toggleIssueState() {
if (this.isOpen) {
this.closeIssue()
- .then(() => this.enableButton())
+ .then(() => {
+ this.enableButton();
+ refreshUserMergeRequestCounts();
+ })
.catch(() => {
this.enableButton();
this.toggleStateButtonLoading(false);
@@ -247,7 +251,10 @@ export default {
});
} else {
this.reopenIssue()
- .then(() => this.enableButton())
+ .then(() => {
+ this.enableButton();
+ refreshUserMergeRequestCounts();
+ })
.catch(({ data }) => {
this.enableButton();
this.toggleStateButtonLoading(false);
diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue
index f4570c1292c..7aa8580d794 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
: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/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 844d0c3e376..6cc873359da 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -165,7 +165,7 @@ export default {
v-gl-tooltip
type="button"
title="Edit comment"
- class="note-action-button js-note-edit btn btn-transparent"
+ class="note-action-button js-note-edit btn btn-transparent qa-note-edit-button"
@click="onEdit"
>
<icon name="pencil" css-classes="link-highlight" />
diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
index be8e42af9ea..1aeb07d6608 100644
--- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue
+++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
@@ -19,7 +19,7 @@ export default {
<gl-button
ref="button"
v-gl-tooltip
- class="note-action-button"
+ class="note-action-button js-note-action-reply"
variant="transparent"
:title="__('Reply to comment')"
@click="$emit('startReplying')"
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index 47a6f07cce2..9e0392110b6 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -9,9 +9,6 @@ export default {
const config = filter !== undefined ? { params: { notes_filter: filter } } : null;
return Vue.http.get(endpoint, config);
},
- deleteNote(endpoint) {
- return Vue.http.delete(endpoint);
- },
replyToDiscussion(endpoint, data) {
return Vue.http.post(endpoint, data, { emulateJSON: true });
},
@@ -38,9 +35,6 @@ export default {
return Vue.http.get(endpoint, options);
},
- toggleAward(endpoint, data) {
- return Vue.http.post(endpoint, data, { emulateJSON: true });
- },
toggleIssueState(endpoint, data) {
return Vue.http.put(endpoint, data);
},
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index fef962f008e..762a87ce0ff 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -62,7 +62,7 @@ export const updateDiscussion = ({ commit, state }, discussion) => {
};
export const deleteNote = ({ commit, dispatch, state }, note) =>
- service.deleteNote(note.path).then(() => {
+ axios.delete(note.path).then(() => {
const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
commit(types.DELETE_NOTE, note);
@@ -357,11 +357,11 @@ export const poll = ({ commit, state, getters, dispatch }) => {
};
export const stopPolling = () => {
- eTagPoll.stop();
+ if (eTagPoll) eTagPoll.stop();
};
export const restartPolling = () => {
- eTagPoll.restart();
+ if (eTagPoll) eTagPoll.restart();
};
export const fetchData = ({ commit, state, getters }) => {
@@ -384,12 +384,9 @@ export const toggleAward = ({ commit, getters }, { awardName, noteId }) => {
export const toggleAwardRequest = ({ dispatch }, data) => {
const { endpoint, awardName } = data;
- return service
- .toggleAward(endpoint, { name: awardName })
- .then(res => res.json())
- .then(() => {
- dispatch('toggleAward', data);
- });
+ return axios.post(endpoint, { name: awardName }).then(() => {
+ dispatch('toggleAward', data);
+ });
};
export const scrollToNoteIfNeeded = (context, el) => {
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 23fb5656008..dcdee77a8ab 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,11 +1,15 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
import { FILTERED_SEARCH } from '~/pages/constants';
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import initManualOrdering from '~/manual_ordering';
+const ISSUE_BULK_UPDATE_PREFIX = 'issue_';
+
document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
+ issuableInitBulkUpdateSidebar.init(ISSUE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
diff --git a/app/assets/javascripts/pages/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/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 941c4552579..2205a7bafe3 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -17,7 +17,5 @@ export default () => {
new MilestoneSelect();
new IssuableTemplateSelectors();
- if (gon.features.graphql) {
- initSuggestions();
- }
+ initSuggestions();
};
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 015c1527500..f05db8376a4 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -42,6 +42,12 @@ export default {
keys: ['feature', 'request'],
},
{
+ metric: 'rugged',
+ header: 'Rugged calls',
+ details: 'details',
+ keys: ['feature', 'args'],
+ },
+ {
metric: 'redis',
header: 'Redis calls',
details: 'details',
diff --git a/app/assets/javascripts/projects/projects_filterable_list.js b/app/assets/javascripts/projects/projects_filterable_list.js
new file mode 100644
index 00000000000..433c894e668
--- /dev/null
+++ b/app/assets/javascripts/projects/projects_filterable_list.js
@@ -0,0 +1,7 @@
+import FilterableList from '~/filterable_list';
+
+export default class ProjectsFilterableList extends FilterableList {
+ getFilterEndpoint() {
+ return this.getPagePath().replace('/projects?', '/projects.json?');
+ }
+}
diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js
index c67d59d2be5..913b62ba26d 100644
--- a/app/assets/javascripts/projects_list.js
+++ b/app/assets/javascripts/projects_list.js
@@ -1,4 +1,4 @@
-import FilterableList from './filterable_list';
+import ProjectsFilterableList from './projects/projects_filterable_list';
/**
* Makes search request for projects when user types a value in the search input.
@@ -11,7 +11,7 @@ export default class ProjectsList {
const holder = document.querySelector('.js-projects-list-holder');
if (form && filter && holder) {
- const list = new FilterableList(form, filter, holder);
+ const list = new ProjectsFilterableList(form, filter, holder);
list.initSearch();
}
}
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
index 7752723baac..38519c220c5 100644
--- a/app/assets/javascripts/registry/components/app.vue
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -2,6 +2,7 @@
import { mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import store from '../stores';
+import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import CollapsibleContainer from './collapsible_container.vue';
import SvgMessage from './svg_message.vue';
import { s__, sprintf } from '../../locale';
@@ -9,6 +10,7 @@ import { s__, sprintf } from '../../locale';
export default {
name: 'RegistryListApp',
components: {
+ clipboardButton,
CollapsibleContainer,
GlLoadingIcon,
SvgMessage,
@@ -46,10 +48,10 @@ export default {
dockerConnectionErrorText() {
return sprintf(
s__(`ContainerRegistry|We are having trouble connecting to Docker, which could be due to an
- issue with your project name or path. For more information, please review the
- %{docLinkStart}Container Registry documentation%{docLinkEnd}.`),
+ issue with your project name or path.
+ %{docLinkStart}More Information%{docLinkEnd}`),
{
- docLinkStart: `<a href="${this.helpPagePath}#docker-connection-error">`,
+ docLinkStart: `<a href="${this.helpPagePath}#docker-connection-error" target="_blank">`,
docLinkEnd: '</a>',
},
false,
@@ -58,10 +60,10 @@ export default {
introText() {
return sprintf(
s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every
- project can have its own space to store its Docker images. Learn more about the
- %{docLinkStart}Container Registry%{docLinkEnd}.`),
+ project can have its own space to store its Docker images.
+ %{docLinkStart}More Information%{docLinkEnd}`),
{
- docLinkStart: `<a href="${this.helpPagePath}">`,
+ docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`,
docLinkEnd: '</a>',
},
false,
@@ -70,14 +72,20 @@ export default {
noContainerImagesText() {
return sprintf(
s__(`ContainerRegistry|With the Container Registry, every project can have its own space to
- store its Docker images. Learn more about the %{docLinkStart}Container Registry%{docLinkEnd}.`),
+ store its Docker images. %{docLinkStart}More Information%{docLinkEnd}`),
{
- docLinkStart: `<a href="${this.helpPagePath}">`,
+ docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`,
docLinkEnd: '</a>',
},
false,
);
},
+ dockerBuildCommand() {
+ return `docker build -t ${this.repositoryUrl} .`;
+ },
+ dockerPushCommand() {
+ return `docker push ${this.repositoryUrl}`;
+ },
},
created() {
this.setMainEndpoint(this.endpoint);
@@ -99,7 +107,7 @@ export default {
<p v-html="dockerConnectionErrorText"></p>
</svg-message>
- <gl-loading-icon v-else-if="isLoading" size="md" class="prepend-top-16" />
+ <gl-loading-icon v-else-if="isLoading && !characterError" size="md" class="prepend-top-16" />
<div v-else-if="!isLoading && !characterError && repos.length">
<h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
@@ -126,10 +134,27 @@ export default {
}}
</p>
- <pre>
- docker build -t {{ repositoryUrl }} .
- docker push {{ repositoryUrl }}
- </pre>
+ <div class="input-group append-bottom-10">
+ <input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="dockerBuildCommand"
+ :title="s__('ContainerRegistry|Copy build command to clipboard')"
+ class="input-group-text"
+ />
+ </span>
+ </div>
+
+ <div class="input-group">
+ <input :value="dockerPushCommand" type="text" class="form-control monospace" readonly />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="dockerPushCommand"
+ :title="s__('ContainerRegistry|Copy push command to clipboard')"
+ class="input-group-text"
+ />
+ </span>
+ </div>
</svg-message>
</div>
</template>
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index 1e266dd4ced..e157036871b 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -31,6 +31,7 @@ export default {
data() {
return {
isOpen: false,
+ modalId: `confirm-repo-deletion-modal-${this.repo.id}`,
};
},
computed: {
@@ -80,7 +81,7 @@ export default {
<gl-button
v-if="repo.canDelete"
v-gl-tooltip
- v-gl-modal="'confirm-repo-deletion-modal'"
+ v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
class="js-remove-repo"
@@ -100,12 +101,7 @@ export default {
{{ s__('ContainerRegistry|No tags in Container Registry for this container image.') }}
</div>
</div>
-
- <gl-modal
- modal-id="confirm-repo-deletion-modal"
- ok-variant="danger"
- @ok="handleDeleteRepository"
- >
+ <gl-modal :modal-id="modalId" ok-variant="danger" @ok="handleDeleteRepository">
<template v-slot:modal-title>{{ s__('ContainerRegistry|Remove repository') }}</template>
<p
v-html="
diff --git a/app/assets/javascripts/registry/components/svg_message.vue b/app/assets/javascripts/registry/components/svg_message.vue
index d0d44bf2d14..617093e054e 100644
--- a/app/assets/javascripts/registry/components/svg_message.vue
+++ b/app/assets/javascripts/registry/components/svg_message.vue
@@ -15,10 +15,12 @@ export default {
</script>
<template>
- <div :id="id" class="empty-state container-message mw-70p">
+ <div :id="id" class="empty-state container-message">
<div class="svg-content">
<img :src="svgPath" class="flex-align-self-center" />
</div>
- <slot></slot>
+ <div class="text-content">
+ <slot></slot>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 0ec5e2c7a87..a498a553908 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -32,6 +32,7 @@ export default {
data() {
return {
itemToBeDeleted: null,
+ modalId: `confirm-image-deletion-modal-${this.repo.id}`,
};
},
computed: {
@@ -114,7 +115,7 @@ export default {
<gl-button
v-if="item.canDelete"
v-gl-tooltip
- v-gl-modal="'confirm-image-deletion-modal'"
+ v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove image')"
:aria-label="s__('ContainerRegistry|Remove image')"
variant="danger"
@@ -134,11 +135,7 @@ export default {
:page-info="repo.pagination"
/>
- <gl-modal
- modal-id="confirm-image-deletion-modal"
- ok-variant="danger"
- @ok="handleDeleteRegistry"
- >
+ <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
diff --git a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
index 6d908524da9..f0112a5a623 100644
--- a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
+++ b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue
@@ -65,7 +65,7 @@ export default {
<template>
<div v-if="isFetchingMergeRequests || (!isFetchingMergeRequests && totalCount)">
- <div id="merge-requests" class="card-slim mt-3">
+ <div id="merge-requests" class="card card-slim mt-3">
<div class="card-header">
<div class="card-title mt-0 mb-0 h5 merge-requests-title">
<span class="mr-1">
diff --git a/app/assets/javascripts/reports/components/issue_status_icon.vue b/app/assets/javascripts/reports/components/issue_status_icon.vue
index 04fba43b2f3..386653b9444 100644
--- a/app/assets/javascripts/reports/components/issue_status_icon.vue
+++ b/app/assets/javascripts/reports/components/issue_status_icon.vue
@@ -16,7 +16,7 @@ export default {
statusIconSize: {
type: Number,
required: false,
- default: 32,
+ default: 24,
},
},
computed: {
diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue
index 2be9c37b00a..f3f7d2648a8 100644
--- a/app/assets/javascripts/reports/components/report_item.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -27,7 +27,7 @@ export default {
statusIconSize: {
type: Number,
required: false,
- default: 32,
+ default: 24,
},
isNew: {
type: Boolean,
@@ -43,12 +43,15 @@ export default {
};
</script>
<template>
- <li :class="{ 'is-dismissed': issue.isDismissed }" class="report-block-list-issue">
+ <li
+ :class="{ 'is-dismissed': issue.isDismissed }"
+ class="report-block-list-issue align-items-center"
+ >
<issue-status-icon
v-if="showReportSectionStatusIcon"
:status="status"
:status-icon-size="statusIconSize"
- class="append-right-5"
+ class="append-right-default"
/>
<component :is="component" v-if="component" :issue="issue" :status="status" :is-new="isNew" />
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 3d576caaf8f..9bc3e6388e3 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -165,8 +165,8 @@ export default {
<template>
<section class="media-section">
<div class="media">
- <status-icon :status="statusIconName" />
- <div class="media-body d-flex flex-align-self-center">
+ <status-icon :status="statusIconName" :size="24" />
+ <div class="media-body d-flex flex-align-self-center prepend-left-default">
<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 97a68531d29..aba798e63d0 100644
--- a/app/assets/javascripts/reports/components/summary_row.vue
+++ b/app/assets/javascripts/reports/components/summary_row.vue
@@ -44,10 +44,14 @@ export default {
};
</script>
<template>
- <div class="report-block-list-issue report-block-list-issue-parent">
- <div class="report-block-list-icon append-right-10 prepend-left-5">
- <gl-loading-icon v-if="statusIcon === 'loading'" css-class="report-block-list-loading-icon" />
- <ci-icon v-else :status="iconStatus" />
+ <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'"
+ css-class="report-block-list-loading-icon"
+ size="md"
+ />
+ <ci-icon v-else :status="iconStatus" :size="24" />
</div>
<div class="report-block-list-issue-description">
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index 67963dc1923..afb58a60155 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -1,12 +1,41 @@
<script>
+import { GlDropdown, GlDropdownDivider, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui';
+import { __ } from '../../locale';
+import Icon from '../../vue_shared/components/icon.vue';
import getRefMixin from '../mixins/get_ref';
import getProjectShortPath from '../queries/getProjectShortPath.query.graphql';
+import getProjectPath from '../queries/getProjectPath.query.graphql';
+import getPermissions from '../queries/getPermissions.query.graphql';
+
+const ROW_TYPES = {
+ header: 'header',
+ divider: 'divider',
+};
export default {
+ components: {
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownHeader,
+ GlDropdownItem,
+ Icon,
+ },
apollo: {
projectShortPath: {
query: getProjectShortPath,
},
+ projectPath: {
+ query: getProjectPath,
+ },
+ userPermissions: {
+ query: getPermissions,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update: data => data.project.userPermissions,
+ },
},
mixins: [getRefMixin],
props: {
@@ -15,10 +44,52 @@ export default {
required: false,
default: '/',
},
+ canCollaborate: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ canEditTree: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ newBranchPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ newTagPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ newBlobPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ forkNewBlobPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ forkNewDirectoryPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ forkUploadBlobPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
projectShortPath: '',
+ projectPath: '',
+ userPermissions: {},
};
},
computed: {
@@ -39,11 +110,112 @@ export default {
[{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }],
);
},
+ canCreateMrFromFork() {
+ return this.userPermissions.forkProject && this.userPermissions.createMergeRequestIn;
+ },
+ dropdownItems() {
+ const items = [];
+
+ if (this.canEditTree) {
+ items.push(
+ {
+ type: ROW_TYPES.header,
+ text: __('This directory'),
+ },
+ {
+ attrs: {
+ href: this.newBlobPath,
+ class: 'qa-new-file-option',
+ },
+ text: __('New file'),
+ },
+ {
+ attrs: {
+ href: '#modal-upload-blob',
+ 'data-target': '#modal-upload-blob',
+ 'data-toggle': 'modal',
+ },
+ text: __('Upload file'),
+ },
+ {
+ attrs: {
+ href: '#modal-create-new-dir',
+ 'data-target': '#modal-create-new-dir',
+ 'data-toggle': 'modal',
+ },
+ text: __('New directory'),
+ },
+ );
+ } else if (this.canCreateMrFromFork) {
+ items.push(
+ {
+ attrs: {
+ href: this.forkNewBlobPath,
+ 'data-method': 'post',
+ },
+ text: __('New file'),
+ },
+ {
+ attrs: {
+ href: this.forkUploadBlobPath,
+ 'data-method': 'post',
+ },
+ text: __('Upload file'),
+ },
+ {
+ attrs: {
+ href: this.forkNewDirectoryPath,
+ 'data-method': 'post',
+ },
+ text: __('New directory'),
+ },
+ );
+ }
+
+ if (this.userPermissions.pushCode) {
+ items.push(
+ {
+ type: ROW_TYPES.divider,
+ },
+ {
+ type: ROW_TYPES.header,
+ text: __('This repository'),
+ },
+ {
+ attrs: {
+ href: this.newBranchPath,
+ },
+ text: __('New branch'),
+ },
+ {
+ attrs: {
+ href: this.newTagPath,
+ },
+ text: __('New tag'),
+ },
+ );
+ }
+
+ return items;
+ },
+ renderAddToTreeDropdown() {
+ return this.canCollaborate || this.canCreateMrFromFork;
+ },
},
methods: {
isLast(i) {
return i === this.pathLinks.length - 1;
},
+ getComponent(type) {
+ switch (type) {
+ case ROW_TYPES.divider:
+ return 'gl-dropdown-divider';
+ case ROW_TYPES.header:
+ return 'gl-dropdown-header';
+ default:
+ return 'gl-dropdown-item';
+ }
+ },
},
};
</script>
@@ -56,6 +228,20 @@ export default {
{{ link.name }}
</router-link>
</li>
+ <li v-if="renderAddToTreeDropdown" class="breadcrumb-item">
+ <gl-dropdown toggle-class="add-to-tree qa-add-to-tree ml-1">
+ <template slot="button-content">
+ <span class="sr-only">{{ __('Add to tree') }}</span>
+ <icon name="plus" :size="16" class="float-left" />
+ <icon name="arrow-down" :size="16" class="float-left" />
+ </template>
+ <template v-for="(item, i) in dropdownItems">
+ <component :is="getComponent(item.type)" :key="i" v-bind="item.attrs">
+ {{ item.text }}
+ </component>
+ </template>
+ </gl-dropdown>
+ </li>
</ol>
</nav>
</template>
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index 1e66ccbfa29..610c7e8d99e 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -76,7 +76,7 @@ export default {
variables: {
projectPath: this.projectPath,
ref: this.ref,
- path: this.path,
+ path: this.path || '/',
nextPageCursor: this.nextPageCursor,
pageSize: PAGE_SIZE,
},
@@ -137,6 +137,7 @@ export default {
:path="entry.flatPath"
:type="entry.type"
:url="entry.webUrl"
+ :submodule-tree-url="entry.treeUrl"
:lfs-oid="entry.lfsOid"
/>
</template>
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 3e060e9ecb6..6029460d975 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -62,6 +62,11 @@ export default {
required: false,
default: null,
},
+ submoduleTreeUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -112,7 +117,7 @@ export default {
</component>
<gl-badge v-if="lfsOid" variant="default" class="label-lfs ml-1">LFS</gl-badge>
<template v-if="isSubmodule">
- @ <gl-link href="#" class="commit-sha">{{ shortSha }}</gl-link>
+ @ <gl-link :href="submoduleTreeUrl" class="commit-sha">{{ shortSha }}</gl-link>
</template>
</td>
<td class="d-none d-sm-table-cell tree-commit">
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index ea051eaa414..f9727960040 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -5,6 +5,7 @@ import Breadcrumbs from './components/breadcrumbs.vue';
import LastCommit from './components/last_commit.vue';
import apolloProvider from './graphql';
import { setTitle } from './utils/title';
+import { parseBoolean } from '../lib/utils/common_utils';
export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list');
@@ -36,19 +37,42 @@ export default function setupVueRepositoryList() {
.forEach(elem => elem.classList.toggle('hidden', !isRoot));
});
- // eslint-disable-next-line no-new
- new Vue({
- el: document.getElementById('js-repo-breadcrumb'),
- router,
- apolloProvider,
- render(h) {
- return h(Breadcrumbs, {
- props: {
- currentPath: this.$route.params.pathMatch,
- },
- });
- },
- });
+ const breadcrumbEl = document.getElementById('js-repo-breadcrumb');
+
+ if (breadcrumbEl) {
+ const {
+ canCollaborate,
+ canEditTree,
+ newBranchPath,
+ newTagPath,
+ newBlobPath,
+ forkNewBlobPath,
+ forkNewDirectoryPath,
+ forkUploadBlobPath,
+ } = breadcrumbEl.dataset;
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: breadcrumbEl,
+ router,
+ apolloProvider,
+ render(h) {
+ return h(Breadcrumbs, {
+ props: {
+ currentPath: this.$route.params.pathMatch,
+ canCollaborate: parseBoolean(canCollaborate),
+ canEditTree: parseBoolean(canEditTree),
+ newBranchPath,
+ newTagPath,
+ newBlobPath,
+ forkNewBlobPath,
+ forkNewDirectoryPath,
+ forkUploadBlobPath,
+ },
+ });
+ },
+ });
+ }
// eslint-disable-next-line no-new
new Vue({
diff --git a/app/assets/javascripts/repository/queries/getFiles.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql
index 4c24fc4087f..b3cc0878cad 100644
--- a/app/assets/javascripts/repository/queries/getFiles.query.graphql
+++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql
@@ -35,6 +35,8 @@ query getFiles(
edges {
node {
...TreeEntry
+ webUrl
+ treeUrl
}
}
pageInfo {
diff --git a/app/assets/javascripts/repository/queries/getPermissions.query.graphql b/app/assets/javascripts/repository/queries/getPermissions.query.graphql
new file mode 100644
index 00000000000..092fa44e2d0
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getPermissions.query.graphql
@@ -0,0 +1,9 @@
+query getPermissions($projectPath: ID!) {
+ project(fullPath: $projectPath) {
+ userPermissions {
+ pushCode
+ forkProject
+ createMergeRequestIn
+ }
+ }
+}
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index 70dc3d2cdfa..be1e4811856 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -2,6 +2,7 @@
import Flash from '~/flash';
import eventHub from '~/sidebar/event_hub';
import Store from '~/sidebar/stores/sidebar_store';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import AssigneeTitle from './assignee_title.vue';
import Assignees from './assignees.vue';
import { __ } from '~/locale';
@@ -73,6 +74,9 @@ export default {
this.mediator
.saveAssignees(this.field)
.then(setLoadingFalse.bind(this))
+ .then(() => {
+ refreshUserMergeRequestCounts();
+ })
.catch(() => {
setLoadingFalse();
return new Flash(__('Error occurred when saving assignees'));
diff --git a/app/assets/javascripts/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/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
index 00a55c0027a..6a7b2f52549 100644
--- a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
+++ b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
@@ -48,6 +48,7 @@
font-size: .8rem;
font-weight: 400;
color: #2e2e2e;
+ z-index: 9999; /* toolbar should always be on top */
}
.gitlab-wrapper-open {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
index 4b57693e8f1..57d4d8b7ae6 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_icon.vue
@@ -14,6 +14,6 @@ export default {
<template>
<div class="circle-icon-container append-right-default align-self-start align-self-lg-center">
- <icon :name="name" />
+ <icon :name="name" :size="24" />
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index f5fa68308bc..40c095aa954 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -96,16 +96,14 @@ export default {
<template>
<div class="ci-widget media js-ci-widget">
<template v-if="!hasPipeline || hasCIError">
- <div
- class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-default"
- >
- <icon :size="32" name="status_failed_borderless" />
+ <div class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error">
+ <icon :size="24" name="status_failed_borderless" />
</div>
- <div class="media-body" v-html="errorText"></div>
+ <div class="media-body prepend-left-default" v-html="errorText"></div>
</template>
<template v-else-if="hasPipeline">
<a :href="status.details_path" class="align-self-start append-right-default">
- <ci-icon :status="status" :size="32" :borderless="true" class="add-border" />
+ <ci-icon :status="status" :size="24" :borderless="true" class="add-border" />
</a>
<div class="ci-widget-container d-flex">
<div class="ci-widget-content">
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 392eb6fb425..8dbd9e52cfe 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,8 +32,8 @@ export default {
};
</script>
<template>
- <div class="space-children d-flex append-right-10 widget-status-icon">
- <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon size="md" /></div>
+ <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" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index 0312b147b62..01524f4b650 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -83,7 +83,7 @@ export default {
<gl-button
:aria-label="ariaLabel"
variant="blank"
- class="commit-edit-toggle square s24 mr-2"
+ class="commit-edit-toggle square s24 append-right-default"
@click.stop="toggle()"
>
<icon :name="collapseIcon" :size="16" />
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
index 7312b31c01c..4d7d49398eb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -18,7 +18,9 @@ export default {
<template>
<div class="mr-widget-body mr-widget-empty-state">
<div class="row">
- <div class="artwork col-md-5 order-md-last col-12 text-center">
+ <div
+ class="artwork col-md-5 order-md-last col-12 text-center d-flex justify-content-center align-items-center"
+ >
<span v-html="emptyStateSVG"></span>
</div>
<div class="text col-md-7 order-md-first col-12">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index d1f75593d14..d4514767912 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -6,6 +6,7 @@ import simplePoll from '~/lib/utils/simple_poll';
import { __ } from '~/locale';
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
import MergeRequest from '../../../merge_request';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
@@ -174,6 +175,8 @@ export default {
MergeRequest.decreaseCounter();
stopPolling();
+ refreshUserMergeRequestCounts();
+
// If user checked remove source branch and we didn't remove the branch yet
// we should start another polling for source branch remove process
if (this.removeSourceBranch && data.source_branch_exists) {
diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js
index 2d1f7a1cfd0..73e92728cb9 100644
--- a/app/assets/javascripts/vue_shared/directives/tooltip.js
+++ b/app/assets/javascripts/vue_shared/directives/tooltip.js
@@ -3,8 +3,12 @@ import '~/commons/bootstrap';
export default {
bind(el) {
+ const glTooltipDelay = localStorage.getItem('gl-tooltip-delay');
+ const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0;
+
$(el).tooltip({
trigger: 'hover',
+ delay,
});
},
diff --git a/app/assets/stylesheets/_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..fbf16aa324a 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -34,4 +34,8 @@
// Styles for JS behaviors.
@import "behaviors";
+// EE-only stylesheets
+@import "application_ee";
+
+// CSS util classes
@import "utilities";
diff --git a/app/assets/stylesheets/components/avatar.scss b/app/assets/stylesheets/components/avatar.scss
index 8e9650cdf34..312123aeef9 100644
--- a/app/assets/stylesheets/components/avatar.scss
+++ b/app/assets/stylesheets/components/avatar.scss
@@ -50,6 +50,11 @@ $avatar-sizes: (
line-height: 88px,
border-radius: $border-radius-large
),
+ 96: (
+ font-size: 36px,
+ line-height: 94px,
+ border-radius: $border-radius-large
+ ),
100: (
font-size: 36px,
line-height: 98px,
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
index 8c40c4adb5c..6654553aaa2 100644
--- a/app/assets/stylesheets/components/popover.scss
+++ b/app/assets/stylesheets/components/popover.scss
@@ -102,6 +102,7 @@
.onboarding-popover {
box-shadow: 0 2px 4px $dropdown-shadow-color;
+ max-width: 280px;
.popover-body {
font-size: $gl-font-size;
diff --git a/app/assets/stylesheets/components/toast.scss b/app/assets/stylesheets/components/toast.scss
index acbd909d595..e27bf282247 100644
--- a/app/assets/stylesheets/components/toast.scss
+++ b/app/assets/stylesheets/components/toast.scss
@@ -15,11 +15,15 @@
.toasted.gl-toast {
border-radius: $border-radius-default;
font-size: $gl-font-size;
- padding: $gl-padding-8 $gl-padding-24;
+ padding: $gl-padding-8 $gl-padding $gl-padding-8 $gl-padding-24;
margin-top: $toast-default-margin;
line-height: $gl-line-height;
background-color: rgba($gray-900, $toast-background-opacity);
+ span {
+ padding-right: $gl-padding-8;
+ }
+
@include media-breakpoint-down(xs) {
.action:first-of-type {
// Ensures actions buttons are right aligned on mobile
@@ -29,19 +33,14 @@
.action {
color: $blue-300;
- margin: 0 0 0 $toast-action-margin-left;
+ margin: 0 0 0 $toast-default-margin;
text-transform: none;
font-size: $gl-font-size;
-
- &:first-of-type {
- padding-right: 0;
- }
}
.toast-close {
font-size: $default-icon-size;
margin-left: $toast-default-margin;
- padding-left: $gl-padding;
}
}
}
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 9b1d9d51f9c..82b4ec750ff 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -9,7 +9,6 @@
@import 'framework/animations';
@import 'framework/vue_transitions';
-@import 'framework/asciidoctor';
@import 'framework/banner';
@import 'framework/blocks';
@import 'framework/buttons';
diff --git a/app/assets/stylesheets/framework/asciidoctor.scss b/app/assets/stylesheets/framework/asciidoctor.scss
deleted file mode 100644
index 1586265d40e..00000000000
--- a/app/assets/stylesheets/framework/asciidoctor.scss
+++ /dev/null
@@ -1,27 +0,0 @@
-.admonitionblock td.icon {
- width: 1%;
-
- [class^='fa icon-'] {
- @extend .fa-2x;
- }
-
- .icon-note {
- @extend .fa-thumb-tack;
- }
-
- .icon-tip {
- @extend .fa-lightbulb-o;
- }
-
- .icon-warning {
- @extend .fa-exclamation-triangle;
- }
-
- .icon-caution {
- @extend .fa-fire;
- }
-
- .icon-important {
- @extend .fa-exclamation-circle;
- }
-}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 1bd5043ed10..61ab0476c42 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -434,6 +434,7 @@ img.emoji {
/** COMMON SIZING CLASSES **/
.w-0 { width: 0; }
+.w-8em { width: 8em; }
.h-12em { height: 12em; }
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index a12029d2419..29f63e9578d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -63,7 +63,8 @@
margin-top: 11px;
}
-.dropdown-toggle {
+.dropdown-toggle,
+.confidential-merge-request-fork-group .dropdown-toggle {
padding: 6px 8px 6px 10px;
background-color: $white-light;
color: $gl-text-color;
@@ -287,8 +288,8 @@
list-style: none;
padding: 0 1px;
- a:not(.help-link),
- button:not(.btn),
+ a,
+ button,
.menu-item {
@include dropdown-link;
}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 26cbb7f5c13..5984efd1cf8 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -9,14 +9,14 @@
float: right;
margin-right: 0;
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
float: none;
}
}
}
.filters-section {
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
display: inline-block;
}
}
@@ -37,7 +37,7 @@
}
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
.filter-item {
display: block;
margin: 0 0 10px;
@@ -50,12 +50,6 @@
}
.filtered-search-wrapper {
- display: flex;
-
- @include media-breakpoint-down(xs) {
- flex-direction: column;
- }
-
.tokens-container {
display: flex;
flex: 1;
@@ -186,7 +180,7 @@
border: 1px solid $border-color;
background-color: $white-light;
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
flex: 1 1 auto;
margin-bottom: 10px;
}
@@ -259,7 +253,7 @@
max-width: 280px;
overflow: auto;
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
width: auto;
left: 0;
right: 0;
@@ -311,7 +305,7 @@
.filtered-search-history-dropdown {
width: 40%;
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(sm) {
left: 0;
right: 0;
max-width: none;
@@ -341,35 +335,46 @@
}
.filter-dropdown-container {
- display: flex;
-
.dropdown-toggle {
line-height: 22px;
}
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
.issues-details-filters {
- padding: 0 0 10px;
+ padding-top: 0;
+ padding-bottom: 0;
background-color: $white-light;
border-top: 0;
}
- .filter-dropdown-container {
+ .boards-switcher {
+ margin: 0 0 10px;
+
+ .boards-selector-wrapper,
.dropdown {
- margin-left: 0;
+ display: block;
}
}
-}
-@include media-breakpoint-down(sm) {
- .filter-dropdown-container {
- .dropdown-toggle,
- .dropdown,
- .dropdown-menu {
+ .filter-dropdown-container > div {
+ margin: 0;
+
+ > .btn {
+ margin: 0 0 10px;
width: 100%;
}
}
+
+ .boards-add-list > .btn {
+ text-align: left;
+
+ > svg {
+ position: absolute;
+ top: 11px;
+ right: 6px;
+ }
+ }
}
.droplab-dropdown .dropdown-menu .filter-dropdown-item {
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 1be5ef276fd..7332c4981d2 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -88,8 +88,5 @@
display: flex;
align-items: center;
justify-content: center;
- border: $border-size solid $gray-400;
- border-radius: 50%;
- padding: $gl-padding-8 - $border-size;
color: $gray-700;
}
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 954551fef97..460d9ea9526 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -265,7 +265,6 @@ ul.controls {
}
.issuable-pipeline-broken a,
- .issuable-pipeline-status a,
.author-link {
display: flex;
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index f75e5b55506..fd9a75bc5b6 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -19,23 +19,31 @@
}
}
- // leave enough space for the close icon
.modal-title {
+ line-height: $gl-line-height-24;
+
+ // leave enough space for the close icon
&.mw-100,
&.w-100 {
- // after upgrading to Bootstrap 4.2 we can use $modal-header-padding-x here
- // https://github.com/twbs/bootstrap/pull/26976
- margin-right: -28px;
- padding-right: 28px;
+ margin-right: -$modal-header-padding-x;
+ padding-right: $modal-header-padding-x;
}
}
+
+ .close {
+ font-weight: $gl-font-weight-normal;
+ line-height: $gl-line-height;
+ color: $gray-900;
+ opacity: 1;
+ }
}
.modal-body {
background-color: $modal-body-bg;
line-height: $line-height-base;
position: relative;
- padding: #{3 * $grid-size} #{2 * $grid-size};
+ min-height: $modal-body-height;
+ padding: #{2 * $grid-size} #{6 * $grid-size} #{2 * $grid-size} #{2 * $grid-size};
text-align: left;
white-space: normal;
@@ -52,12 +60,20 @@
display: flex;
flex-direction: row;
+ .btn {
+ margin: 0;
+ }
+
.btn + .btn:not(.dropdown-toggle-split),
.btn + .btn-group,
.btn-group + .btn {
margin-left: $grid-size;
}
+ .btn-group .btn + .btn {
+ margin-left: -1px;
+ }
+
@include media-breakpoint-down(xs) {
flex-direction: column;
@@ -67,6 +83,11 @@
margin-left: 0;
margin-top: $grid-size;
}
+
+ .btn-group .btn + .btn {
+ margin-left: -1px;
+ margin-top: 0;
+ }
}
}
@@ -85,9 +106,23 @@ body.modal-open {
.modal {
background-color: $black-transparent;
- @include media-breakpoint-up(md) {
+ .modal-content {
+ border-radius: $modal-border-radius;
+
+ > :first-child {
+ border-top-left-radius: $modal-border-radius;
+ border-top-right-radius: $modal-border-radius;
+ }
+
+ > :last-child {
+ border-bottom-left-radius: $modal-border-radius;
+ border-bottom-right-radius: $modal-border-radius;
+ }
+ }
+
+ @include media-breakpoint-up(sm) {
.modal-dialog {
- margin: 30px auto;
+ margin: 64px auto;
}
}
}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index cd3d6f8297e..d9c93fed1c4 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -3,7 +3,6 @@
}
.card-slim {
- @extend .card;
margin-bottom: $gl-vert-padding;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 7baab478034..c201605e83d 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -1,5 +1,5 @@
/**
- * Apply Markdown typography
+ * Apply Markup (Markdown/AsciiDoc) typography
*
*/
.md:not(.use-csslab) {
@@ -245,6 +245,21 @@
}
}
+ ul.checklist,
+ ul.none,
+ ol.none,
+ ul.no-bullet,
+ ol.no-bullet,
+ ol.unnumbered,
+ ul.unstyled,
+ ol.unstyled {
+ list-style-type: none;
+
+ li {
+ margin-left: 0;
+ }
+ }
+
li {
line-height: 1.6em;
margin-left: 25px;
@@ -321,6 +336,54 @@
visibility: visible;
}
}
+
+ .big {
+ font-size: larger;
+ }
+
+ .small {
+ font-size: smaller;
+ }
+
+ .underline {
+ text-decoration: underline;
+ }
+
+ .overline {
+ text-decoration: overline;
+ }
+
+ .line-through {
+ text-decoration: line-through;
+ }
+
+ .admonitionblock td.icon {
+ width: 1%;
+
+ [class^='fa icon-'] {
+ @extend .fa-2x;
+ }
+
+ .icon-note {
+ @extend .fa-thumb-tack;
+ }
+
+ .icon-tip {
+ @extend .fa-lightbulb-o;
+ }
+
+ .icon-warning {
+ @extend .fa-exclamation-triangle;
+ }
+
+ .icon-caution {
+ @extend .fa-fire;
+ }
+
+ .icon-important {
+ @extend .fa-exclamation-circle;
+ }
+ }
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b6a24247d40..c108f45622f 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -507,7 +507,6 @@ $toast-height: 48px;
$toast-max-width: 586px;
$toast-padding-right: 42px;
$toast-default-margin: 8px;
-$toast-action-margin-left: 16px;
$toast-background-opacity: 0.95;
/*
@@ -805,8 +804,9 @@ $border-color-settings: #e1e1e1;
/*
Modals
*/
-$modal-body-height: 134px;
+$modal-body-height: 80px;
$modal-border-color: #e9ecef;
+$modal-border-radius: 0.25rem;
$priority-label-empty-state-width: 114px;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index e12ea6fcb99..0b0a4e50146 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -167,7 +167,7 @@
min-width: 0;
.project-namespace {
- color: $gl-text-color-secondary;
+ color: $gl-text-color-tertiary;
}
}
diff --git a/app/assets/stylesheets/pages/container_registry.scss b/app/assets/stylesheets/pages/container_registry.scss
index cca5214a508..a21fa29f34a 100644
--- a/app/assets/stylesheets/pages/container_registry.scss
+++ b/app/assets/stylesheets/pages/container_registry.scss
@@ -6,6 +6,10 @@
pre {
white-space: pre-line;
}
+
+ span .btn {
+ margin: 0;
+ }
}
.container-image {
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 3ffe8ae304d..95ea49ad465 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -14,7 +14,7 @@
position: -webkit-sticky;
position: sticky;
top: $mr-file-header-top;
- z-index: 220;
+ z-index: 120;
&::before {
content: '';
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/help.scss b/app/assets/stylesheets/pages/help.scss
index 7610c5cf6f3..ef872e693e0 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -38,3 +38,9 @@
.documentation {
padding: 7px;
}
+
+.card.links-card {
+ a {
+ color: $blue-600;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6a0127eb51c..66b4f3bad2b 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -390,7 +390,7 @@
.block {
width: $gutter-collapsed-width - 2px;
- padding: 15px 0 0;
+ padding: 0;
border-bottom: 0;
overflow: hidden;
@@ -427,10 +427,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 +473,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 +506,6 @@
.sidebar-collapsed-user {
padding-bottom: 0;
- margin-bottom: 10px;
.author-link {
padding-left: 0;
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 11e8a32389f..7d5e185834b 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -30,6 +30,10 @@
.dropdown-content {
max-height: 135px;
}
+
+ .dropdown-label-box {
+ flex: 0 0 auto;
+ }
}
.dropdown-new-label {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 3917937f4af..2780afa11fa 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -10,8 +10,8 @@
float: left;
}
- > *:not(:last-child) {
- margin-right: 10px;
+ > *:not(:first-child) {
+ margin-left: 10px;
}
}
@@ -69,7 +69,7 @@
content: '';
border-left: 1px solid $gray-200;
position: absolute;
- left: 32px;
+ left: 28px;
top: -17px;
height: 16px;
}
@@ -114,7 +114,7 @@
padding: $gl-padding;
@include media-breakpoint-up(md) {
- padding-left: $gl-padding-50;
+ padding-left: $gl-padding-8 * 7;
}
}
}
@@ -264,6 +264,10 @@
.widget-status-icon {
align-self: flex-start;
+
+ button {
+ margin-left: $gl-padding;
+ }
}
.mr-widget-body {
@@ -271,8 +275,8 @@
@include clearfix;
- &.media > *:first-child {
- margin-right: 10px;
+ button {
+ margin-left: $gl-padding;
}
.approve-btn {
@@ -312,6 +316,7 @@
.bold {
font-weight: $gl-font-weight-bold;
color: $gl-gray-light;
+ margin-left: 10px;
}
.state-label {
@@ -377,9 +382,13 @@
&.mr-widget-empty-state {
line-height: 20px;
+ padding: $gl-padding;
.artwork {
- margin-bottom: $gl-padding;
+
+ @include media-breakpoint-down(md) {
+ margin-bottom: $gl-padding;
+ }
}
.text {
@@ -395,7 +404,7 @@
}
.mr-widget-help {
- padding: 10px 16px 10px $gl-padding-50;
+ padding: 10px 16px 10px ($gl-padding-8 * 7);
font-style: italic;
}
@@ -913,7 +922,7 @@
.media-body {
min-width: 0;
font-size: 12px;
- margin-left: 48px;
+ margin-left: 40px;
}
&:not(:last-child) {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index aa6bbc8e473..5f4db37c317 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -588,8 +588,8 @@
}
.ci-status-icon svg {
- height: 20px;
- width: 20px;
+ height: 24px;
+ width: 24px;
}
.dropdown-menu-toggle {
@@ -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..05a4cc168a8 100644
--- a/app/assets/stylesheets/pages/prometheus.scss
+++ b/app/assets/stylesheets/pages/prometheus.scss
@@ -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 94da72622af..85e9f303dde 100644
--- a/app/assets/stylesheets/pages/reports.scss
+++ b/app/assets/stylesheets/pages/reports.scss
@@ -57,7 +57,7 @@
.report-block-container {
border-top: 1px solid $border-color;
- padding: $gl-padding-top;
+ padding: $gl-padding - 2;
background-color: $gray-light;
// Clean MR widget CSS
@@ -96,17 +96,14 @@
.ci-status-icon {
svg {
- width: 16px;
- height: 16px;
- left: -2px;
+ width: 24px;
+ height: 24px;
}
}
}
.report-block-list-issue {
display: flex;
- align-items: flex-start;
- align-content: flex-start;
}
.is-dismissed .report-block-list-issue-description,
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 10120a472d3..60400f10ca5 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -168,6 +168,10 @@
}
ul.wiki-pages-list.content-list {
+ a {
+ color: $blue-600;
+ }
+
ul {
list-style: none;
margin-left: 0;
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index a570da61d54..e9ec8876688 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -103,7 +103,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
[
*::ApplicationSettingsHelper.visible_attributes,
*::ApplicationSettingsHelper.external_authorization_service_attributes,
- *lets_encrypt_visible_attributes,
+ :lets_encrypt_notification_email,
+ :lets_encrypt_terms_of_service_accepted,
:domain_blacklist_file,
disabled_oauth_sign_in_sources: [],
import_sources: [],
@@ -143,13 +144,4 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
render action
end
-
- def lets_encrypt_visible_attributes
- return [] unless Feature.enabled?(:pages_auto_ssl)
-
- [
- :lets_encrypt_notification_email,
- :lets_encrypt_terms_of_service_accepted
- ]
- end
end
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..0c80a276fce 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -499,9 +499,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 +507,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/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 0dd7500623d..90528f75ffd 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -2,16 +2,24 @@
module Boards
class IssuesController < Boards::ApplicationController
+ # This is the maximum amount of issues which can be moved by one request to
+ # bulk_move for now. This is temporary and might be removed in future by
+ # introducing an alternative (async?) approach.
+ # (related: https://gitlab.com/groups/gitlab-org/-/epics/382)
+ MAX_MOVE_ISSUES_COUNT = 50
+
include BoardsResponses
include ControllerWithCrossProjectAccessCheck
requires_cross_project_access if: -> { board&.group_board? }
- before_action :whitelist_query_limiting, only: [:index, :update]
+ before_action :whitelist_query_limiting, only: [:index, :update, :bulk_move]
before_action :authorize_read_issue, only: [:index]
before_action :authorize_create_issue, only: [:create]
before_action :authorize_update_issue, only: [:update]
skip_before_action :authenticate_user!, only: [:index]
+ before_action :validate_id_list, only: [:bulk_move]
+ before_action :can_move_issues?, only: [:bulk_move]
# rubocop: disable CodeReuse/ActiveRecord
def index
@@ -46,6 +54,14 @@ module Boards
end
end
+ def bulk_move
+ service = Boards::Issues::MoveService.new(board_parent, current_user, move_params(true))
+
+ issues = Issue.find(params[:ids])
+
+ render json: service.execute_multiple(issues)
+ end
+
def update
service = Boards::Issues::MoveService.new(board_parent, current_user, move_params)
@@ -58,6 +74,10 @@ module Boards
private
+ def can_move_issues?
+ head(:forbidden) unless can?(current_user, :admin_issue, board)
+ end
+
def render_issues(issues, metadata)
data = { issues: serialize_as_json(issues) }
data.merge!(metadata)
@@ -90,8 +110,9 @@ module Boards
end
end
- def move_params
- params.permit(:board_id, :id, :from_list_id, :to_list_id, :move_before_id, :move_after_id)
+ def move_params(multiple = false)
+ id_param = multiple ? :ids : :id
+ params.permit(id_param, :board_id, :from_list_id, :to_list_id, :move_before_id, :move_after_id)
end
def issue_params
@@ -112,5 +133,10 @@ module Boards
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42439
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42428')
end
+
+ def validate_id_list
+ head(:bad_request) unless params[:ids].is_a?(Array)
+ head(:unprocessable_entity) if params[:ids].size > MAX_MOVE_ISSUES_COUNT
+ end
end
end
diff --git a/app/controllers/chaos_controller.rb b/app/controllers/chaos_controller.rb
index 8d518c14b90..ac008165c16 100644
--- a/app/controllers/chaos_controller.rb
+++ b/app/controllers/chaos_controller.rb
@@ -1,56 +1,83 @@
# frozen_string_literal: true
class ChaosController < ActionController::Base
- before_action :validate_request
+ before_action :validate_chaos_secret, unless: :development_or_test?
def leakmem
- memory_mb = (params[:memory_mb]&.to_i || 100)
- duration_s = (params[:duration_s]&.to_i || 30).seconds
+ do_chaos :leak_mem, Chaos::LeakMemWorker, memory_mb, duration_s
+ end
- start = Time.now
- retainer = []
- # Add `n` 1mb chunks of memory to the retainer array
- memory_mb.times { retainer << "x" * 1.megabyte }
+ def cpu_spin
+ do_chaos :cpu_spin, Chaos::CpuSpinWorker, duration_s
+ end
- duration_taken = (Time.now - start).seconds
- Kernel.sleep duration_s - duration_taken if duration_s > duration_taken
+ def db_spin
+ do_chaos :db_spin, Chaos::DbSpinWorker, duration_s, interval_s
+ end
- render plain: "OK"
+ def sleep
+ do_chaos :sleep, Chaos::SleepWorker, duration_s
end
- def cpuspin
- duration_s = (params[:duration_s]&.to_i || 30).seconds
- end_time = Time.now + duration_s.seconds
+ def kill
+ do_chaos :kill, Chaos::KillWorker
+ end
- rand while Time.now < end_time
+ private
+
+ def do_chaos(method, worker, *args)
+ if async
+ worker.perform_async(*args)
+ else
+ Gitlab::Chaos.public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend
+ end
render plain: "OK"
end
- def sleep
- duration_s = (params[:duration_s]&.to_i || 30).seconds
- Kernel.sleep duration_s
+ def validate_chaos_secret
+ unless chaos_secret_configured
+ render plain: "chaos misconfigured: please configure GITLAB_CHAOS_SECRET",
+ status: :internal_server_error
+ return
+ end
- render plain: "OK"
+ unless Devise.secure_compare(chaos_secret_configured, chaos_secret_request)
+ render plain: "To experience chaos, please set a valid `X-Chaos-Secret` header or `token` param",
+ status: :unauthorized
+ return
+ end
end
- def kill
- Process.kill("KILL", Process.pid)
+ def chaos_secret_configured
+ ENV['GITLAB_CHAOS_SECRET']
end
- private
+ def chaos_secret_request
+ request.headers["HTTP_X_CHAOS_SECRET"] || params[:token]
+ end
- def validate_request
- secret = ENV['GITLAB_CHAOS_SECRET']
- # GITLAB_CHAOS_SECRET is required unless you're running in Development mode
- if !secret && !Rails.env.development?
- render plain: "chaos misconfigured: please configure GITLAB_CHAOS_SECRET when using GITLAB_ENABLE_CHAOS_ENDPOINTS outside of a development environment", status: :internal_server_error
- end
+ def interval_s
+ interval_s = params[:interval_s] || 1
+ interval_s.to_f.seconds
+ end
- return unless secret
+ def duration_s
+ duration_s = params[:duration_s] || 30
+ duration_s.to_i.seconds
+ end
- unless request.headers["HTTP_X_CHAOS_SECRET"] == secret
- render plain: "To experience chaos, please set X-Chaos-Secret header", status: :unauthorized
- end
+ def memory_mb
+ memory_mb = params[:memory_mb] || 100
+ memory_mb.to_i
+ end
+
+ 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/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 065d2d3a4ec..6fa2f75be33 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -92,7 +92,7 @@ module IssuableActions
end
def bulk_update
- result = Issuable::BulkUpdateService.new(project, current_user, bulk_update_params).execute(resource_name)
+ result = Issuable::BulkUpdateService.new(current_user, bulk_update_params).execute(resource_name)
quantity = result[:count]
render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
@@ -181,7 +181,7 @@ module IssuableActions
end
def authorize_admin_issuable!
- unless can?(current_user, :"admin_#{resource_name}", @project) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ unless can?(current_user, :"admin_#{resource_name}", parent)
return access_denied!
end
end
diff --git a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
index f47ead2f0da..2e9905997db 100644
--- a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
+++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb
@@ -28,7 +28,7 @@ module RequiresWhitelistedMonitoringClient
def valid_token?
token = params[:token].presence || request.headers['TOKEN']
token.present? &&
- ActiveSupport::SecurityUtils.variable_size_secure_compare(
+ ActiveSupport::SecurityUtils.secure_compare(
token,
Gitlab::CurrentSettings.health_check_access_token
)
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index 1ce0afac83b..9fbbe373b0d 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -11,7 +11,6 @@ class GraphqlController < ApplicationController
# around in GraphiQL.
protect_from_forgery with: :null_session, only: :execute
- before_action :check_graphql_feature_flag!
before_action :authorize_access_api!
before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
@@ -86,8 +85,4 @@ class GraphqlController < ApplicationController
render json: error, status: status
end
-
- def check_graphql_feature_flag!
- render_404 unless Gitlab::Graphql.enabled?
- end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 797833e3f91..dda321bac79 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -8,7 +8,7 @@ class GroupsController < Groups::ApplicationController
include RecordUserLastActivity
before_action do
- push_frontend_feature_flag(:manual_sorting)
+ push_frontend_feature_flag(:manual_sorting, default_enabled: true)
end
respond_to :html
@@ -107,7 +107,7 @@ class GroupsController < Groups::ApplicationController
if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group, anchor: params[:update_section]), notice: "Group '#{@group.name}' was successfully updated."
else
- @group.restore_path!
+ @group.path = @group.path_before_last_save || @group.path_was
render action: "edit"
end
@@ -192,7 +192,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/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb
index fb43356ff10..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.new(project, 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 8c071496ba9..2d46a71bf99 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -9,7 +9,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
before_action :authorize_read_cycle_analytics!
def show
- @cycle_analytics = ::CycleAnalytics.new(@project, options(cycle_analytics_params))
+ @cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_params))
@cycle_analytics_no_data = @cycle_analytics.no_stats?
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index ecf05e6ea64..ccd54b369fa 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -13,6 +13,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint)
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
+ push_frontend_feature_flag(:environment_metrics_additional_panel_types)
push_frontend_feature_flag(:prometheus_computed_alerts)
end
@@ -159,20 +160,22 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics_dashboard
- return render_403 unless Feature.enabled?(:environment_metrics_use_prometheus_endpoint, project)
-
- if Feature.enabled?(:environment_metrics_show_multiple_dashboards, project)
+ if Feature.enabled?(:gfm_embedded_metrics, project) && params[:embedded]
result = dashboard_finder.find(
project,
current_user,
environment,
- dashboard_path: params[:dashboard],
embedded: params[:embedded]
)
+ elsif Feature.enabled?(:environment_metrics_show_multiple_dashboards, project)
+ result = dashboard_finder.find(
+ project,
+ current_user,
+ environment,
+ dashboard_path: params[:dashboard]
+ )
- unless params[:embedded]
- result[:all_dashboards] = dashboard_finder.find_all_paths(project)
- end
+ result[:all_dashboards] = dashboard_finder.find_all_paths(project)
else
result = dashboard_finder.find(project, current_user, environment)
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b866f574f67..db7ca7ef0d7 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -11,7 +11,7 @@ class Projects::IssuesController < Projects::ApplicationController
include RecordUserLastActivity
before_action do
- push_frontend_feature_flag(:manual_sorting)
+ push_frontend_feature_flag(:manual_sorting, default_enabled: true)
end
def issue_except_actions
@@ -45,8 +45,6 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_import_issues!, only: [:import_csv]
before_action :authorize_download_code!, only: [:related_branches]
- before_action :set_suggested_issues_feature_flags, only: [:new]
-
respond_to :html
def index
@@ -285,8 +283,4 @@ class Projects::IssuesController < Projects::ApplicationController
# 3. https://gitlab.com/gitlab-org/gitlab-ce/issues/42426
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42422')
end
-
- def set_suggested_issues_feature_flags
- push_frontend_feature_flag(:graphql, default_enabled: true)
- end
end
diff --git a/app/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/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/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index fa5bdbc7d49..d1914c35bd3 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -6,11 +6,12 @@ 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
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index feefc7f8137..37ffd28bf9e 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]
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index b2b151bbcf0..638934694e0 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -8,8 +8,7 @@ class RegistrationsController < Devise::RegistrationsController
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)
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index a841859621e..7604b31467a 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -13,18 +13,17 @@ 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?
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
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/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/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 152ebb930e2..7edd14e48f7 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -66,6 +66,8 @@ class GitlabSchema < GraphQL::Schema
if gid.model_class < ApplicationRecord
Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find
+ elsif gid.model_class.respond_to?(:lazy_find)
+ gid.model_class.lazy_find(gid.model_id)
else
gid.find
end
diff --git a/app/graphql/mutations/award_emojis/base.rb b/app/graphql/mutations/award_emojis/base.rb
index d868db84f9d..583744c3884 100644
--- a/app/graphql/mutations/award_emojis/base.rb
+++ b/app/graphql/mutations/award_emojis/base.rb
@@ -3,8 +3,6 @@
module Mutations
module AwardEmojis
class Base < BaseMutation
- include Gitlab::Graphql::Authorize::AuthorizeResource
-
authorize :award_emoji
argument :awardable_id,
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index 08d2a1f18a3..7273a74cb86 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -2,6 +2,7 @@
module Mutations
class BaseMutation < GraphQL::Schema::RelayClassicMutation
+ prepend Gitlab::Graphql::Authorize::AuthorizeResource
prepend Gitlab::Graphql::CopyFieldDescription
field :errors, [GraphQL::STRING_TYPE],
diff --git a/app/graphql/mutations/merge_requests/base.rb b/app/graphql/mutations/merge_requests/base.rb
index e85d16fc2c5..28e0cdc8cc7 100644
--- a/app/graphql/mutations/merge_requests/base.rb
+++ b/app/graphql/mutations/merge_requests/base.rb
@@ -3,7 +3,6 @@
module Mutations
module MergeRequests
class Base < BaseMutation
- include Gitlab::Graphql::Authorize::AuthorizeResource
include Mutations::ResolvesProject
argument :project_path, GraphQL::ID_TYPE,
diff --git a/app/graphql/mutations/notes/base.rb b/app/graphql/mutations/notes/base.rb
new file mode 100644
index 00000000000..31dabc0a660
--- /dev/null
+++ b/app/graphql/mutations/notes/base.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ class Base < BaseMutation
+ field :note,
+ Types::Notes::NoteType,
+ null: true,
+ description: 'The note after mutation'
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id)
+ end
+
+ def check_object_is_noteable!(object)
+ unless object.is_a?(Noteable)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Cannot add notes to this resource'
+ end
+ end
+
+ def check_object_is_note!(object)
+ unless object.is_a?(Note)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Resource is not a note'
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb
new file mode 100644
index 00000000000..d3a5dae2188
--- /dev/null
+++ b/app/graphql/mutations/notes/create/base.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Create
+ # This is a Base class for the Note creation Mutations and is not
+ # mounted as a GraphQL mutation itself.
+ class Base < Mutations::Notes::Base
+ authorize :create_note
+
+ argument :noteable_id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the resource to add a note to'
+
+ argument :body,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :body)
+
+ private
+
+ def resolve(args)
+ noteable = authorized_find!(id: args[:noteable_id])
+
+ check_object_is_noteable!(noteable)
+
+ note = ::Notes::CreateService.new(
+ noteable.project,
+ current_user,
+ create_note_params(noteable, args)
+ ).execute
+
+ {
+ note: (note if note.persisted?),
+ errors: errors_on_object(note)
+ }
+ end
+
+ def create_note_params(noteable, args)
+ {
+ noteable: noteable,
+ note: args[:body]
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/diff_note.rb b/app/graphql/mutations/notes/create/diff_note.rb
new file mode 100644
index 00000000000..9b5f3092006
--- /dev/null
+++ b/app/graphql/mutations/notes/create/diff_note.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Create
+ class DiffNote < Base
+ graphql_name 'CreateDiffNote'
+
+ argument :position,
+ Types::Notes::DiffPositionInputType,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :position)
+
+ private
+
+ def create_note_params(noteable, args)
+ super(noteable, args).merge({
+ type: 'DiffNote',
+ position: position(noteable, args)
+ })
+ end
+
+ def position(noteable, args)
+ position = args[:position].to_h
+ position[:position_type] = 'text'
+ position.merge!(position[:paths].to_h)
+
+ Gitlab::Diff::Position.new(position)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/image_diff_note.rb b/app/graphql/mutations/notes/create/image_diff_note.rb
new file mode 100644
index 00000000000..d94fd4d6ff8
--- /dev/null
+++ b/app/graphql/mutations/notes/create/image_diff_note.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Create
+ class ImageDiffNote < Base
+ graphql_name 'CreateImageDiffNote'
+
+ argument :position,
+ Types::Notes::DiffImagePositionInputType,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :position)
+
+ private
+
+ def create_note_params(noteable, args)
+ super(noteable, args).merge({
+ type: 'DiffNote',
+ position: position(noteable, args)
+ })
+ end
+
+ def position(noteable, args)
+ position = args[:position].to_h
+ position[:position_type] = 'image'
+ position.merge!(position[:paths].to_h)
+
+ Gitlab::Diff::Position.new(position)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/note.rb b/app/graphql/mutations/notes/create/note.rb
new file mode 100644
index 00000000000..5236e48026e
--- /dev/null
+++ b/app/graphql/mutations/notes/create/note.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Create
+ class Note < Base
+ graphql_name 'CreateNote'
+
+ argument :discussion_id,
+ GraphQL::ID_TYPE,
+ required: false,
+ description: 'The global id of the discussion this note is in reply to'
+
+ private
+
+ def create_note_params(noteable, args)
+ discussion_id = nil
+
+ if args[:discussion_id]
+ discussion = GitlabSchema.object_from_id(args[:discussion_id])
+ authorize_discussion!(discussion)
+
+ discussion_id = discussion.id
+ end
+
+ super(noteable, args).merge({
+ in_reply_to_discussion_id: discussion_id
+ })
+ end
+
+ def authorize_discussion!(discussion)
+ unless Ability.allowed?(current_user, :read_note, discussion, scope: :user)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ "The discussion does not exist or you don't have permission to perform this action"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/destroy.rb b/app/graphql/mutations/notes/destroy.rb
new file mode 100644
index 00000000000..a81322bc9b7
--- /dev/null
+++ b/app/graphql/mutations/notes/destroy.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ class Destroy < Base
+ graphql_name 'DestroyNote'
+
+ authorize :admin_note
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the note to destroy'
+
+ def resolve(id:)
+ note = authorized_find!(id: id)
+
+ check_object_is_note!(note)
+
+ ::Notes::DestroyService.new(note.project, current_user).execute(note)
+
+ {
+ errors: []
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/update.rb b/app/graphql/mutations/notes/update.rb
new file mode 100644
index 00000000000..ebf57b800c0
--- /dev/null
+++ b/app/graphql/mutations/notes/update.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ class Update < Base
+ graphql_name 'UpdateNote'
+
+ authorize :admin_note
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the note to update'
+
+ argument :body,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :body)
+
+ def resolve(args)
+ note = authorized_find!(id: args[:id])
+
+ check_object_is_note!(note)
+
+ note = ::Notes::UpdateService.new(
+ note.project,
+ current_user,
+ { note: args[:body] }
+ ).execute(note)
+
+ {
+ note: note.reset,
+ errors: errors_on_object(note)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/base_input_object.rb b/app/graphql/types/base_input_object.rb
index aebed035d3b..90a29b0cfb8 100644
--- a/app/graphql/types/base_input_object.rb
+++ b/app/graphql/types/base_input_object.rb
@@ -2,5 +2,6 @@
module Types
class BaseInputObject < GraphQL::Schema::InputObject
+ prepend Gitlab::Graphql::CopyFieldDescription
end
end
diff --git a/app/graphql/types/diff_paths_input_type.rb b/app/graphql/types/diff_paths_input_type.rb
new file mode 100644
index 00000000000..43feddd9827
--- /dev/null
+++ b/app/graphql/types/diff_paths_input_type.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class DiffPathsInputType < BaseInputObject
+ argument :old_path, GraphQL::STRING_TYPE, required: false,
+ description: 'The path of the file on the start sha'
+ argument :new_path, GraphQL::STRING_TYPE, required: false,
+ description: 'The path of the file on the head sha'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/diff_refs_type.rb b/app/graphql/types/diff_refs_type.rb
new file mode 100644
index 00000000000..33a5780cd68
--- /dev/null
+++ b/app/graphql/types/diff_refs_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ # Types that use DiffRefsType should have their own authorization
+ class DiffRefsType < BaseObject
+ graphql_name 'DiffRefs'
+
+ field :head_sha, GraphQL::STRING_TYPE, null: false, description: 'The sha of the head at the time the comment was made'
+ field :base_sha, GraphQL::STRING_TYPE, null: false, description: 'The merge base of the branch the comment was made on'
+ field :start_sha, GraphQL::STRING_TYPE, null: false, description: 'The sha of the branch being compared against'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 6734d4761c2..b8f63a750c5 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -23,6 +23,7 @@ module Types
field :updated_at, Types::TimeType, null: false
field :source_project, Types::ProjectType, null: true
field :target_project, Types::ProjectType, null: false
+ field :diff_refs, Types::DiffRefsType, null: true
# Alias for target_project
field :project, Types::ProjectType, null: false
field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index bc5fb709522..f843d6ad86f 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -10,5 +10,10 @@ module Types
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true
+ mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
+ mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
+ mount_mutation Mutations::Notes::Create::ImageDiffNote, calls_gitaly: true
+ mount_mutation Mutations::Notes::Update
+ mount_mutation Mutations::Notes::Destroy
end
end
diff --git a/app/graphql/types/notes/diff_image_position_input_type.rb b/app/graphql/types/notes/diff_image_position_input_type.rb
new file mode 100644
index 00000000000..23b53b20815
--- /dev/null
+++ b/app/graphql/types/notes/diff_image_position_input_type.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ # rubocop: disable Graphql/AuthorizeTypes
+ class DiffImagePositionInputType < DiffPositionBaseInputType
+ graphql_name 'DiffImagePositionInput'
+
+ argument :x, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :x)
+ argument :y, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :y)
+ argument :width, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :width)
+ argument :height, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :height)
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/notes/diff_position_base_input_type.rb b/app/graphql/types/notes/diff_position_base_input_type.rb
new file mode 100644
index 00000000000..a9b4e1a8948
--- /dev/null
+++ b/app/graphql/types/notes/diff_position_base_input_type.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ # rubocop: disable Graphql/AuthorizeTypes
+ class DiffPositionBaseInputType < BaseInputObject
+ argument :head_sha, GraphQL::STRING_TYPE, required: true,
+ description: copy_field_description(Types::DiffRefsType, :head_sha)
+ argument :base_sha, GraphQL::STRING_TYPE, required: false,
+ description: copy_field_description(Types::DiffRefsType, :base_sha)
+ argument :start_sha, GraphQL::STRING_TYPE, required: true,
+ description: copy_field_description(Types::DiffRefsType, :start_sha)
+
+ argument :paths,
+ Types::DiffPathsInputType,
+ required: true,
+ description: 'The paths of the file that was changed. ' \
+ 'Both of the properties of this input are optional, but at least one of them is required'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/notes/diff_position_input_type.rb b/app/graphql/types/notes/diff_position_input_type.rb
new file mode 100644
index 00000000000..02c91e173cb
--- /dev/null
+++ b/app/graphql/types/notes/diff_position_input_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ # rubocop: disable Graphql/AuthorizeTypes
+ class DiffPositionInputType < DiffPositionBaseInputType
+ graphql_name 'DiffPositionInput'
+
+ argument :old_line, GraphQL::INT_TYPE, required: false,
+ description: copy_field_description(Types::Notes::DiffPositionType, :old_line)
+ argument :new_line, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :new_line)
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/notes/diff_position_type.rb b/app/graphql/types/notes/diff_position_type.rb
index ebc24451715..6a0377fbfdf 100644
--- a/app/graphql/types/notes/diff_position_type.rb
+++ b/app/graphql/types/notes/diff_position_type.rb
@@ -7,12 +7,7 @@ module Types
class DiffPositionType < BaseObject
graphql_name 'DiffPosition'
- field :head_sha, GraphQL::STRING_TYPE, null: false,
- description: "The sha of the head at the time the comment was made"
- field :base_sha, GraphQL::STRING_TYPE, null: true,
- description: "The merge base of the branch the comment was made on"
- field :start_sha, GraphQL::STRING_TYPE, null: false,
- description: "The sha of the branch being compared against"
+ field :diff_refs, Types::DiffRefsType, null: false
field :file_path, GraphQL::STRING_TYPE, null: false,
description: "The path of the file that was changed"
diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb
index c4691942f2d..a3fb28298f6 100644
--- a/app/graphql/types/notes/discussion_type.rb
+++ b/app/graphql/types/notes/discussion_type.rb
@@ -8,8 +8,16 @@ module Types
authorize :read_note
field :id, GraphQL::ID_TYPE, null: false
+ field :reply_id, GraphQL::ID_TYPE, null: false, description: 'The ID used to reply to this discussion'
field :created_at, Types::TimeType, null: false
field :notes, Types::Notes::NoteType.connection_type, null: false, description: "All notes in the discussion"
+
+ # The gem we use to generate Global IDs is hard-coded to work with
+ # `id` properties. To generate a GID for the `reply_id` property,
+ # we must use the ::Gitlab::GlobalId module.
+ def reply_id
+ ::Gitlab::GlobalId.build(object, id: object.reply_id)
+ end
end
end
end
diff --git a/app/graphql/types/tree/submodule_type.rb b/app/graphql/types/tree/submodule_type.rb
index 8cb1e04f5ba..2b47e5c0161 100644
--- a/app/graphql/types/tree/submodule_type.rb
+++ b/app/graphql/types/tree/submodule_type.rb
@@ -7,6 +7,9 @@ module Types
implements Types::Tree::EntryType
graphql_name 'Submodule'
+
+ field :web_url, type: GraphQL::STRING_TYPE, null: true
+ field :tree_url, type: GraphQL::STRING_TYPE, null: true
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/tree/tree_type.rb b/app/graphql/types/tree/tree_type.rb
index fbdc1597461..99f2a6c0235 100644
--- a/app/graphql/types/tree/tree_type.rb
+++ b/app/graphql/types/tree/tree_type.rb
@@ -15,7 +15,9 @@ module Types
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository)
end
- field :submodules, Types::Tree::SubmoduleType.connection_type, null: false, calls_gitaly: true
+ field :submodules, Types::Tree::SubmoduleType.connection_type, null: false, calls_gitaly: true, resolve: -> (obj, args, ctx) do
+ Gitlab::Graphql::Representation::SubmoduleTreeEntry.decorate(obj.submodules, obj)
+ end
field :blobs, Types::Tree::BlobType.connection_type, null: false, calls_gitaly: true, resolve: -> (obj, args, ctx) do
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.blobs, obj.repository)
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 0d6a6496993..4b0713001a1 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -18,7 +18,16 @@ module BlobHelper
end
def ide_edit_path(project = @project, ref = @ref, path = @path, options = {})
- segments = [ide_path, 'project', project.full_path, 'edit', ref]
+ project_path =
+ if !current_user || can?(current_user, :push_code, project)
+ project.full_path
+ else
+ # We currently always fork to the user's namespace
+ # in edit_fork_button_tag
+ "#{current_user.namespace.full_path}/#{project.path}"
+ end
+
+ segments = [ide_path, 'project', project_path, 'edit', ref]
segments.concat(['-', encode_ide_path(path)]) if path.present?
File.join(segments)
end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 8ef68018d23..bbe05f40999 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -99,7 +99,11 @@ module BoardsHelper
recent_project_boards_path(@project) if current_board_parent.is_a?(Project)
end
+ def serializer
+ CurrentBoardSerializer.new
+ end
+
def current_board_json
- board.to_json
+ serializer.represent(board).as_json
end
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/icons_helper.rb b/app/helpers/icons_helper.rb
index 4e11772b252..4f73270577f 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -122,27 +122,30 @@ module IconsHelper
icon_class = 'file-pdf-o'
when '.jpg', '.jpeg', '.jif', '.jfif',
'.jp2', '.jpx', '.j2k', '.j2c',
- '.png', '.gif', '.tif', '.tiff',
- '.svg', '.ico', '.bmp'
+ '.apng', '.png', '.gif', '.tif', '.tiff',
+ '.svg', '.ico', '.bmp', '.webp'
icon_class = 'file-image-o'
- when '.zip', '.zipx', '.tar', '.gz', '.bz', '.bzip',
- '.xz', '.rar', '.7z'
+ when '.zip', '.zipx', '.tar', '.gz', '.gzip', '.tgz', '.bz', '.bzip',
+ '.bz2', '.bzip2', '.car', '.tbz', '.xz', 'txz', '.rar', '.7z',
+ '.lz', '.lzma', '.tlz'
icon_class = 'file-archive-o'
- when '.mp3', '.wma', '.ogg', '.oga', '.wav', '.flac', '.aac'
+ when '.mp3', '.wma', '.ogg', '.oga', '.wav', '.flac', '.aac', '.3ga',
+ '.ac3', '.midi', '.m4a', '.ape', '.mpa'
icon_class = 'file-audio-o'
when '.mp4', '.m4p', '.m4v',
'.mpg', '.mp2', '.mpeg', '.mpe', '.mpv',
- '.mpg', '.mpeg', '.m2v',
+ '.mpg', '.mpeg', '.m2v', '.m2ts',
'.avi', '.mkv', '.flv', '.ogv', '.mov',
'.3gp', '.3g2'
icon_class = 'file-video-o'
- when '.doc', '.dot', '.docx', '.docm', '.dotx', '.dotm', '.docb'
+ when '.doc', '.dot', '.docx', '.docm', '.dotx', '.dotm', '.docb',
+ '.odt', '.ott', '.uot', '.rtf'
icon_class = 'file-word-o'
when '.xls', '.xlt', '.xlm', '.xlsx', '.xlsm', '.xltx', '.xltm',
- '.xlsb', '.xla', '.xlam', '.xll', '.xlw'
+ '.xlsb', '.xla', '.xlam', '.xll', '.xlw', '.ots', '.ods', '.uos'
icon_class = 'file-excel-o'
when '.ppt', '.pot', '.pps', '.pptx', '.pptm', '.potx', '.potm',
- '.ppam', '.ppsx', '.ppsm', '.sldx', '.sldm'
+ '.ppam', '.ppsx', '.ppsm', '.sldx', '.sldm', '.odp', '.otp', '.uop'
icon_class = 'file-powerpoint-o'
else
icon_class = 'file-text-o'
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 67685ba4e1d..e2e007eee50 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -282,6 +282,10 @@ module IssuablesHelper
data[:hasClosingMergeRequest] = issuable.merge_requests_count(current_user) != 0 if issuable.is_a?(Issue)
+ zoom_links = Gitlab::ZoomLinkExtractor.new(issuable.description).links
+
+ data[:zoomMeetingUrl] = zoom_links.last if zoom_links.any?
+
if parent.is_a?(Group)
data[:groupPath] = parent.path
else
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 8ccb39f8444..d76a0f3a3b8 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -3,7 +3,7 @@
require 'nokogiri'
module MarkupHelper
- include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TextHelper
include ::Gitlab::ActionViewOutput::Context
def plain?(filename)
@@ -154,9 +154,7 @@ module MarkupHelper
elsif asciidoc?(file_name)
asciidoc_unsafe(text, context)
elsif plain?(file_name)
- content_tag :pre, class: 'plain-readme' do
- text
- end
+ plain_unsafe(text)
else
other_markup_unsafe(file_name, text, context)
end
@@ -271,6 +269,12 @@ module MarkupHelper
Gitlab::Asciidoc.render(text, context)
end
+ def plain_unsafe(text)
+ content_tag :pre, class: 'plain-readme' do
+ text
+ end
+ end
+
def other_markup_unsafe(file_name, text, context = {})
Gitlab::OtherMarkup.render(file_name, text, context)
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 8dee842a22d..8d0079a4dd3 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -662,6 +662,6 @@ module ProjectsHelper
end
def vue_file_list_enabled?
- Gitlab::Graphql.enabled? && Feature.enabled?(:vue_file_list, @project)
+ Feature.enabled?(:vue_file_list, @project)
end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 26692934456..d5e5b472115 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -31,22 +31,24 @@ module SortingHelper
end
def projects_sort_options_hash
- Feature.enabled?(:project_list_filter_bar) && !current_controller?('admin/projects') ? projects_sort_common_options_hash : old_projects_sort_options_hash
- end
+ use_old_sorting = Feature.disabled?(:project_list_filter_bar) || current_controller?('admin/projects')
- # TODO: Simplify these sorting options
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/60798
- # https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/11209#note_162234858
- def old_projects_sort_options_hash
options = {
sort_value_latest_activity => sort_title_latest_activity,
+ sort_value_recently_created => sort_title_created_date,
sort_value_name => sort_title_name,
- sort_value_oldest_activity => sort_title_oldest_activity,
- sort_value_oldest_created => sort_title_oldest_created,
- sort_value_recently_created => sort_title_recently_created,
- sort_value_stars_desc => sort_title_most_stars
+ sort_value_stars_desc => sort_title_stars
}
+ if use_old_sorting
+ options = options.merge({
+ sort_value_oldest_activity => sort_title_oldest_activity,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_stars_desc => sort_title_most_stars
+ })
+ end
+
if current_controller?('admin/projects')
options[sort_value_largest_repo] = sort_title_largest_repo
end
@@ -54,26 +56,14 @@ module SortingHelper
options
end
- def projects_sort_common_options_hash
- {
- sort_value_latest_activity => sort_title_latest_activity,
- sort_value_recently_created => sort_title_created_date,
- sort_value_name => sort_title_name,
- sort_value_stars_desc => sort_title_stars
- }
- end
-
def projects_sort_option_titles
- {
- sort_value_latest_activity => sort_title_latest_activity,
- sort_value_recently_created => sort_title_created_date,
- sort_value_name => sort_title_name,
- sort_value_stars_desc => sort_title_stars,
+ # Only used for the project filter search bar
+ projects_sort_options_hash.merge({
sort_value_oldest_activity => sort_title_latest_activity,
sort_value_oldest_created => sort_title_created_date,
sort_value_name_desc => sort_title_name,
sort_value_stars_asc => sort_title_stars
- }
+ })
end
def projects_reverse_sort_options_hash
@@ -210,47 +200,42 @@ module SortingHelper
sort_options_hash[sort_value]
end
- def issuable_sort_icon_suffix(sort_value)
+ def sort_direction_icon(sort_value)
case sort_value
when sort_value_milestone, sort_value_due_date, /_asc\z/
- 'lowest'
+ 'sort-lowest'
else
- 'highest'
+ 'sort-highest'
end
end
- # TODO: dedupicate issuable and project sort direction
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/60798
- def issuable_sort_direction_button(sort_value)
+ def sort_direction_button(reverse_url, reverse_sort, sort_value)
link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
- reverse_sort = issuable_reverse_sort_order_hash[sort_value]
+ icon = sort_direction_icon(sort_value)
+ url = reverse_url
- if reverse_sort
- reverse_url = page_filter_path(sort: reverse_sort)
- else
- reverse_url = '#'
+ unless reverse_sort
+ url = '#'
link_class += ' disabled'
end
- link_to(reverse_url, type: 'button', class: link_class, title: s_('SortOptions|Sort direction')) do
- sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16)
+ link_to(url, type: 'button', class: link_class, title: s_('SortOptions|Sort direction')) do
+ sprite_icon(icon, size: 16)
end
end
+ def issuable_sort_direction_button(sort_value)
+ reverse_sort = issuable_reverse_sort_order_hash[sort_value]
+ url = page_filter_path(sort: reverse_sort)
+
+ sort_direction_button(url, reverse_sort, sort_value)
+ end
+
def project_sort_direction_button(sort_value)
- link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
reverse_sort = projects_reverse_sort_options_hash[sort_value]
+ url = filter_projects_path(sort: reverse_sort)
- if reverse_sort
- reverse_url = filter_projects_path(sort: reverse_sort)
- else
- reverse_url = '#'
- link_class += ' disabled'
- end
-
- link_to(reverse_url, type: 'button', class: link_class, title: s_('SortOptions|Sort direction')) do
- sprite_icon("sort-#{issuable_sort_icon_suffix(sort_value)}", size: 16)
- end
+ sort_direction_button(url, reverse_sort, sort_value)
end
# Titles.
diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb
index ecf37bae6b3..ce810433a3a 100644
--- a/app/helpers/storage_helper.rb
+++ b/app/helpers/storage_helper.rb
@@ -17,6 +17,6 @@ module StorageHelper
counter_lfs_objects: storage_counter(statistics.lfs_objects_size)
}
- _("%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS") % counters
+ _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}") % counters
end
end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 164c69ca50b..9a281065b90 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -9,6 +9,10 @@ module SubmoduleHelper
def submodule_links(submodule_item, ref = nil, repository = @repository)
url = repository.submodule_url_for(ref, submodule_item.path)
+ submodule_links_for_url(submodule_item.id, url, repository)
+ end
+
+ def submodule_links_for_url(submodule_item_id, url, repository)
if url == '.' || url == './'
url = File.join(Gitlab.config.gitlab.url, repository.project.full_path)
end
@@ -30,14 +34,14 @@ 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)
+ relative_self_links(url, submodule_item_id, repository.project)
elsif github_dot_com_url?(url)
- standard_links('github.com', namespace, project, submodule_item.id)
+ standard_links('github.com', namespace, project, submodule_item_id)
elsif gitlab_dot_com_url?(url)
- standard_links('gitlab.com', namespace, project, submodule_item.id)
+ standard_links('gitlab.com', namespace, project, submodule_item_id)
else
[sanitize_submodule_url(url), nil]
end
@@ -95,8 +99,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]
@@ -114,4 +118,8 @@ module SubmoduleHelper
rescue URI::InvalidURIError
nil
end
+
+ def url_helpers
+ Gitlab::Routing.url_helpers
+ end
end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 4690b6ffbe1..bb1cdcb1b31 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -147,4 +147,43 @@ module TreeHelper
def relative_url_root
Gitlab.config.gitlab.relative_url_root.presence || '/'
end
+
+ # project and path are used on the EE version
+ def tree_content_data(logs_path, project, path)
+ {
+ "logs-path" => logs_path
+ }
+ end
+
+ def breadcrumb_data_attributes
+ attrs = {
+ can_collaborate: can_collaborate_with_project?(@project).to_s,
+ new_blob_path: project_new_blob_path(@project, @id),
+ new_branch_path: new_project_branch_path(@project),
+ new_tag_path: new_project_tag_path(@project),
+ can_edit_tree: can_edit_tree?.to_s
+ }
+
+ if can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
+ continue_param = {
+ to: project_new_blob_path(@project, @id),
+ notice: edit_in_new_fork_notice,
+ notice_now: edit_in_new_fork_notice_now
+ }
+
+ attrs.merge!(
+ fork_new_blob_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param),
+ fork_new_directory_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param.merge({
+ to: request.fullpath,
+ notice: _("%{edit_in_new_fork_notice} Try to create a new directory again.") % { edit_in_new_fork_notice: edit_in_new_fork_notice }
+ })),
+ fork_upload_blob_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param.merge({
+ to: request.fullpath,
+ notice: _("%{edit_in_new_fork_notice} Try to upload a file again.") % { edit_in_new_fork_notice: edit_in_new_fork_notice }
+ }))
+ )
+ end
+
+ attrs
+ end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index b318b27992a..2bd803c0177 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -65,20 +65,6 @@ module VisibilityLevelHelper
end
end
- def restricted_visibility_level_description(level)
- level_name = Gitlab::VisibilityLevel.level_name(level)
- _("%{level_name} visibility has been restricted by the administrator.") % { level_name: level_name.capitalize }
- end
-
- def disallowed_visibility_level_description(level, form_model)
- case form_model
- when Project
- disallowed_project_visibility_level_description(level, form_model)
- when Group
- disallowed_group_visibility_level_description(level, form_model)
- end
- end
-
# Note: these messages closely mirror the form validation strings found in the project
# model and any changes or additons to these may also need to be made there.
def disallowed_project_visibility_level_description(level, project)
@@ -181,6 +167,14 @@ module VisibilityLevelHelper
[requested_level, max_allowed_visibility_level(form_model)].min
end
+ def multiple_visibility_levels_restricted?
+ restricted_visibility_levels.many? # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ def all_visibility_levels_restricted?
+ Gitlab::VisibilityLevel.values == restricted_visibility_levels
+ end
+
private
def max_allowed_visibility_level(form_model)
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 576caea4c10..8ef20a03541 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -78,17 +78,7 @@ class Notify < BaseMailer
#
# 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
-
- 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.find(recipient_id).notification_email_for(notification_group)
end
# Formats arguments into a String suitable for use as an email subject
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index f355b02c428..fdd210f0fba 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -3,6 +3,8 @@
class ActiveSession
include ActiveModel::Model
+ SESSION_BATCH_SIZE = 200
+
attr_accessor :created_at, :updated_at,
:session_id, :ip_address,
:browser, :os, :device_name, :device_type,
@@ -91,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
@@ -106,10 +108,12 @@ class ActiveSession
Gitlab::Redis::SharedState.with do |redis|
session_keys = session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" }
- redis.mget(session_keys).compact.map do |raw_session|
- # rubocop:disable Security/MarshalLoad
- Marshal.load(raw_session)
- # rubocop:enable Security/MarshalLoad
+ session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch|
+ redis.mget(session_keys_batch).compact.map do |raw_session|
+ # rubocop:disable Security/MarshalLoad
+ Marshal.load(raw_session)
+ # rubocop:enable Security/MarshalLoad
+ end
end
end
end
@@ -125,7 +129,7 @@ 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.
diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb
index 52b5a7b4a91..28aab279545 100644
--- a/app/models/chat_team.rb
+++ b/app/models/chat_team.rb
@@ -12,6 +12,6 @@ class ChatTeam < ApplicationRecord
# Either the group is not found, or the user doesn't have the proper
# access on the mattermost instance. In the first case, we're done either way
# in the latter case, we can't recover by retrying, so we just log what happened
- Rails.logger.error("Mattermost team deletion failed: #{e}")
+ Rails.logger.error("Mattermost team deletion failed: #{e}") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 89cc082d0bc..da70cb9a9a7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -266,7 +266,7 @@ module Ci
begin
Ci::Build.retry(build, build.user)
rescue Gitlab::Access::AccessDeniedError => ex
- Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}"
+ Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}" # rubocop:disable Gitlab/RailsLogger
end
end
end
@@ -531,6 +531,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
@@ -578,7 +586,7 @@ module Ci
end
def valid_token?(token)
- self.token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
+ self.token && ActiveSupport::SecurityUtils.secure_compare(token, self.token)
end
def has_tags?
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/pipeline.rb b/app/models/ci/pipeline.rb
index 20ca4a9ab24..c2eb51ba100 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -196,7 +196,7 @@ module Ci
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
query = ApplicationRecord.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
- order(query)
+ order(Arel.sql(query))
end
scope :for_user, -> (user) { where(user: user) }
@@ -236,8 +236,6 @@ module Ci
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
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index ba8cea0cea9..42d4e86fe8d 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -4,11 +4,8 @@ module Ci
class PipelineSchedule < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
- include IgnorableColumn
include StripAttribute
- ignore_column :deleted_at
-
belongs_to :project
belongs_to :owner, class_name: 'User'
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
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/trigger.rb b/app/models/ci/trigger.rb
index 8927bb9bc18..8792c5cf98b 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -3,17 +3,15 @@
module Ci
class Trigger < ApplicationRecord
extend Gitlab::Ci::Model
- include IgnorableColumn
include Presentable
- ignore_column :deleted_at
-
belongs_to :project
belongs_to :owner, class_name: "User"
has_many :trigger_requests
validates :token, presence: true, uniqueness: true
+ validates :owner, presence: true, unless: :supports_legacy_tokens?
before_validation :set_default_values
@@ -37,8 +35,13 @@ module Ci
self.owner_id.blank?
end
+ def supports_legacy_tokens?
+ Feature.enabled?(:use_legacy_pipeline_triggers, project)
+ end
+
def can_access_project?
- self.owner_id.blank? || Ability.allowed?(self.owner, :create_build, project)
+ supports_legacy_tokens? && legacy? ||
+ Ability.allowed?(self.owner, :create_build, project)
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index f0256ff4d41..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'
@@ -29,13 +29,6 @@ module Clusters
content_values.to_yaml
end
- # Need to investigate if pipelines run by this runner will stop upon the
- # executor pod stopping
- # I.e.run a pipeline, and uninstall runner while pipeline is running
- def allowed_to_uninstall?
- false
- end
-
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -47,6 +40,14 @@ module Clusters
)
end
+ def prepare_uninstall
+ runner&.update!(active: false)
+ end
+
+ def post_uninstall
+ runner.destroy!
+ end
+
private
def ensure_runner
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
index 4514498b84b..803a65726d3 100644
--- a/app/models/clusters/concerns/application_core.rb
+++ b/app/models/clusters/concerns/application_core.rb
@@ -46,6 +46,16 @@ module Clusters
command.version = version
end
end
+
+ def prepare_uninstall
+ # Override if your application needs any action before
+ # being uninstalled by Helm
+ end
+
+ def post_uninstall
+ # Override if your application needs any action after
+ # being uninstalled by Helm
+ end
end
end
end
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index 54a3dda6d75..342d766f723 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -59,29 +59,33 @@ module Clusters
transition [:scheduled] => :uninstalling
end
- before_transition any => [:scheduled] do |app_status, _|
- app_status.status_reason = nil
+ before_transition any => [:scheduled] do |application, _|
+ application.status_reason = nil
end
- before_transition any => [:errored] do |app_status, transition|
+ before_transition any => [:errored] do |application, transition|
status_reason = transition.args.first
- app_status.status_reason = status_reason if status_reason
+ application.status_reason = status_reason if status_reason
end
- before_transition any => [:updating] do |app_status, _|
- app_status.status_reason = nil
+ before_transition any => [:updating] do |application, _|
+ application.status_reason = nil
end
- before_transition any => [:update_errored, :uninstall_errored] do |app_status, transition|
+ before_transition any => [:update_errored, :uninstall_errored] do |application, transition|
status_reason = transition.args.first
- app_status.status_reason = status_reason if status_reason
+ application.status_reason = status_reason if status_reason
end
- before_transition any => [:installed, :updated] do |app_status, _|
+ before_transition any => [:installed, :updated] do |application, _|
# When installing any application we are also performing an update
# of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so
# therefore we need to reflect that in the database.
- app_status.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
+ application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
+ end
+
+ after_transition any => [:uninstalling], :use_transactions => false do |application, _|
+ application.prepare_uninstall
end
end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 42203a5f214..9713e79f525 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -87,6 +87,16 @@ module CacheMarkdownField
__send__(cached_markdown_fields.html_field(markdown_field)) # rubocop:disable GitlabSecurity/PublicSend
end
+ # Updates the markdown cache if necessary, then returns the field
+ # Unlike `cached_html_for` it returns `nil` if the field does not exist
+ def updated_cached_html_for(markdown_field)
+ return unless cached_markdown_fields.markdown_fields.include?(markdown_field)
+
+ refresh_markdown_cache if attribute_invalidated?(cached_markdown_fields.html_field(markdown_field))
+
+ cached_html_for(markdown_field)
+ end
+
def latest_cached_markdown_version
@latest_cached_markdown_version ||= (Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16) | local_version
end
@@ -139,8 +149,9 @@ module CacheMarkdownField
# The HTML becomes invalid if any dependent fields change. For now, assume
# author and project invalidate the cache in all circumstances.
define_method(invalidation_method) do
- invalidations = changed_markdown_fields & [markdown_field.to_s, *INVALIDATED_BY]
- invalidations.delete(markdown_field.to_s) if changed_markdown_fields.include?("#{markdown_field}_html")
+ changed_fields = changed_attributes.keys
+ invalidations = changed_fields & [markdown_field.to_s, *INVALIDATED_BY]
+ invalidations.delete(markdown_field.to_s) if changed_fields.include?("#{markdown_field}_html")
!invalidations.empty? || !cached_html_up_to_date?(markdown_field)
end
end
diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb
index 8cbf4bcfaf7..53dff2adfc3 100644
--- a/app/models/concerns/cacheable_attributes.rb
+++ b/app/models/concerns/cacheable_attributes.rb
@@ -49,7 +49,7 @@ module CacheableAttributes
current_without_cache.tap { |current_record| current_record&.cache! }
rescue => e
if Rails.env.production?
- Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}")
+ Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}") # rubocop:disable Gitlab/RailsLogger
else
raise e
end
diff --git a/app/models/concerns/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/contextable.rb b/app/models/concerns/ci/contextable.rb
index e1d5ce7f7d4..91dda803031 100644
--- a/app/models/concerns/ci/contextable.rb
+++ b/app/models/concerns/ci/contextable.rb
@@ -59,6 +59,7 @@ module Ci
variables.append(key: 'CI', value: 'true')
variables.append(key: 'GITLAB_CI', value: 'true')
variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(','))
+ variables.append(key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host)
variables.append(key: 'CI_SERVER_NAME', value: 'GitLab')
variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION)
variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s)
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 8f28c897eb6..e1a8725e728 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -12,19 +12,10 @@ module DeploymentPlatform
private
def find_deployment_platform(environment)
- find_platform_kubernetes(environment) ||
+ find_platform_kubernetes_with_cte(environment) ||
find_instance_cluster_platform_kubernetes(environment: environment)
end
- def find_platform_kubernetes(environment)
- if Feature.enabled?(:clusters_cte)
- find_platform_kubernetes_with_cte(environment)
- else
- find_cluster_platform_kubernetes(environment: environment) ||
- find_group_cluster_platform_kubernetes(environment: environment)
- end
- end
-
# EE would override this and utilize environment argument
def find_platform_kubernetes_with_cte(_environment)
Clusters::ClustersHierarchy.new(self).base_and_ancestors
@@ -33,18 +24,6 @@ module DeploymentPlatform
end
# EE would override this and utilize environment argument
- def find_cluster_platform_kubernetes(environment: nil)
- clusters.enabled.default_environment
- .last&.platform_kubernetes
- end
-
- # EE would override this and utilize environment argument
- def find_group_cluster_platform_kubernetes(environment: nil)
- Clusters::Cluster.enabled.default_environment.ancestor_clusters_for_clusterable(self)
- .first&.platform_kubernetes
- end
-
- # EE would override this and utilize environment argument
def find_instance_cluster_platform_kubernetes(environment: nil)
Clusters::Instance.new.clusters.enabled.default_environment
.first&.platform_kubernetes
diff --git a/app/models/concerns/from_union.rb b/app/models/concerns/from_union.rb
index 9b8595b1211..e28dee34815 100644
--- a/app/models/concerns/from_union.rb
+++ b/app/models/concerns/from_union.rb
@@ -40,11 +40,7 @@ module FromUnion
.new(members, remove_duplicates: remove_duplicates)
.to_sql
- # This pattern is necessary as a bug in Rails 4 can cause the use of
- # `from("string here").includes(:foo)` to break ActiveRecord. This is
- # fixed in https://github.com/rails/rails/pull/25374, which is released as
- # part of Rails 5.
- from([Arel.sql("(#{union}) #{alias_as}")])
+ from(Arel.sql("(#{union}) #{alias_as}"))
end
# rubocop: enable Gitlab/Union
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 78bcce2f592..27a5c3d5286 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -33,22 +33,24 @@ module HasStatus
canceled = scope_relevant.canceled.select('count(*)').to_sql
warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false'
- "(CASE
- WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success'
- WHEN (#{builds})=(#{skipped}) THEN 'skipped'
- WHEN (#{builds})=(#{success}) THEN 'success'
- WHEN (#{builds})=(#{created}) THEN 'created'
- WHEN (#{builds})=(#{preparing}) THEN 'preparing'
- WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
- WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
- WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
- WHEN (#{running})+(#{pending})>0 THEN 'running'
- WHEN (#{manual})>0 THEN 'manual'
- WHEN (#{scheduled})>0 THEN 'scheduled'
- WHEN (#{preparing})>0 THEN 'preparing'
- WHEN (#{created})>0 THEN 'running'
- ELSE 'failed'
- END)"
+ Arel.sql(
+ "(CASE
+ WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success'
+ WHEN (#{builds})=(#{skipped}) THEN 'skipped'
+ WHEN (#{builds})=(#{success}) THEN 'success'
+ WHEN (#{builds})=(#{created}) THEN 'created'
+ WHEN (#{builds})=(#{preparing}) THEN 'preparing'
+ WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
+ WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
+ WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
+ WHEN (#{running})+(#{pending})>0 THEN 'running'
+ WHEN (#{manual})>0 THEN 'manual'
+ WHEN (#{scheduled})>0 THEN 'scheduled'
+ WHEN (#{preparing})>0 THEN 'preparing'
+ WHEN (#{created})>0 THEN 'running'
+ ELSE 'failed'
+ END)"
+ )
end
def status
@@ -88,22 +90,22 @@ module HasStatus
state :scheduled, value: 'scheduled'
end
- scope :created, -> { where(status: 'created') }
- scope :preparing, -> { where(status: 'preparing') }
- scope :relevant, -> { where(status: AVAILABLE_STATUSES - ['created']) }
- scope :running, -> { where(status: 'running') }
- scope :pending, -> { where(status: 'pending') }
- scope :success, -> { where(status: 'success') }
- scope :failed, -> { where(status: 'failed') }
- scope :canceled, -> { where(status: 'canceled') }
- scope :skipped, -> { where(status: 'skipped') }
- scope :manual, -> { where(status: 'manual') }
- scope :scheduled, -> { where(status: 'scheduled') }
- scope :alive, -> { where(status: [:created, :preparing, :pending, :running]) }
- scope :created_or_pending, -> { where(status: [:created, :pending]) }
- scope :running_or_pending, -> { where(status: [:running, :pending]) }
- scope :finished, -> { where(status: [:success, :failed, :canceled]) }
- scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
+ scope :created, -> { with_status(:created) }
+ scope :preparing, -> { with_status(:preparing) }
+ scope :relevant, -> { without_status(:created) }
+ scope :running, -> { with_status(:running) }
+ scope :pending, -> { with_status(:pending) }
+ scope :success, -> { with_status(:success) }
+ scope :failed, -> { with_status(:failed) }
+ scope :canceled, -> { with_status(:canceled) }
+ scope :skipped, -> { with_status(:skipped) }
+ scope :manual, -> { with_status(:manual) }
+ scope :scheduled, -> { with_status(:scheduled) }
+ scope :alive, -> { with_status(:created, :preparing, :pending, :running) }
+ scope :created_or_pending, -> { with_status(:created, :pending) }
+ scope :running_or_pending, -> { with_status(:running, :pending) }
+ scope :finished, -> { with_status(:success, :failed, :canceled) }
+ scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
scope :cancelable, -> do
where(status: [:running, :preparing, :pending, :created, :scheduled])
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/mentionable.rb b/app/models/concerns/mentionable.rb
index 0d88b34fb48..2f3f9b399d9 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -63,6 +63,9 @@ module Mentionable
skip_project_check: skip_project_check?
).merge(mentionable_params)
+ cached_html = self.try(:updated_cached_html_for, attr.to_sym)
+ options[:rendered] = cached_html if cached_html
+
extractor.analyze(text, options)
end
diff --git a/app/models/concerns/project_api_compatibility.rb b/app/models/concerns/project_api_compatibility.rb
new file mode 100644
index 00000000000..631b2a11e9a
--- /dev/null
+++ b/app/models/concerns/project_api_compatibility.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# Add methods used by the projects API
+module ProjectAPICompatibility
+ extend ActiveSupport::Concern
+
+ def build_git_strategy=(value)
+ write_attribute(:build_allow_git_fetch, value == 'fetch')
+ end
+
+ def auto_devops_enabled=(value)
+ (auto_devops || build_auto_devops).enabled = value
+ end
+
+ def auto_devops_deploy_strategy=(value)
+ (auto_devops || build_auto_devops).deploy_strategy = value
+ end
+end
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index f268a842db4..551a2e56ecf 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -9,32 +9,70 @@ require 'gitlab/utils'
module ProjectFeaturesCompatibility
extend ActiveSupport::Concern
+ # TODO: remove in API v5, replaced by *_access_level
def wiki_enabled=(value)
- write_feature_attribute(:wiki_access_level, value)
+ write_feature_attribute_boolean(:wiki_access_level, value)
end
+ # TODO: remove in API v5, replaced by *_access_level
def builds_enabled=(value)
- write_feature_attribute(:builds_access_level, value)
+ write_feature_attribute_boolean(:builds_access_level, value)
end
+ # TODO: remove in API v5, replaced by *_access_level
def merge_requests_enabled=(value)
- write_feature_attribute(:merge_requests_access_level, value)
+ write_feature_attribute_boolean(:merge_requests_access_level, value)
end
+ # TODO: remove in API v5, replaced by *_access_level
def issues_enabled=(value)
- write_feature_attribute(:issues_access_level, value)
+ write_feature_attribute_boolean(:issues_access_level, value)
end
+ # TODO: remove in API v5, replaced by *_access_level
def snippets_enabled=(value)
- write_feature_attribute(:snippets_access_level, value)
+ write_feature_attribute_boolean(:snippets_access_level, value)
+ end
+
+ def repository_access_level=(value)
+ write_feature_attribute_string(:repository_access_level, value)
+ end
+
+ def wiki_access_level=(value)
+ write_feature_attribute_string(:wiki_access_level, value)
+ end
+
+ def builds_access_level=(value)
+ write_feature_attribute_string(:builds_access_level, value)
+ end
+
+ def merge_requests_access_level=(value)
+ write_feature_attribute_string(:merge_requests_access_level, value)
+ end
+
+ def issues_access_level=(value)
+ write_feature_attribute_string(:issues_access_level, value)
+ end
+
+ def snippets_access_level=(value)
+ write_feature_attribute_string(:snippets_access_level, value)
end
private
- def write_feature_attribute(field, value)
+ def write_feature_attribute_boolean(field, value)
+ access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
+ write_feature_attribute_raw(field, access_level)
+ end
+
+ def write_feature_attribute_string(field, value)
+ access_level = ProjectFeature.access_level_from_str(value)
+ write_feature_attribute_raw(field, access_level)
+ end
+
+ def write_feature_attribute_raw(field, value)
build_project_feature unless project_feature
- access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
- project_feature.__send__(:write_attribute, field, access_level) # rubocop:disable GitlabSecurity/PublicSend
+ project_feature.__send__(:write_attribute, field, value) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index 22b6b1d720c..e4fe46d722a 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -179,7 +179,7 @@ module RelativePositioning
relation = yield relation if block_given?
relation
- .pluck(self.class.parent_column, "#{calculation}(relative_position) AS position")
+ .pluck(self.class.parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
.first&.
last
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index b9ffc64e4a9..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 = "(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/stepable.rb b/app/models/concerns/stepable.rb
new file mode 100644
index 00000000000..d00a049a004
--- /dev/null
+++ b/app/models/concerns/stepable.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Stepable
+ extend ActiveSupport::Concern
+
+ def steps
+ self.class._all_steps
+ end
+
+ def execute_steps
+ initial_result = {}
+
+ steps.inject(initial_result) do |previous_result, callback|
+ result = method(callback).call
+
+ if result[:status] == :error
+ result[:failed_step] = callback
+
+ break result
+ end
+
+ previous_result.merge(result)
+ end
+ end
+
+ class_methods do
+ def _all_steps
+ @_all_steps ||= []
+ end
+
+ def steps(*methods)
+ _all_steps.concat methods
+ end
+ end
+end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index a15dc19e07a..78544405c49 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -64,7 +64,7 @@ module Storage
unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
- Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}"
+ Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
diff --git a/app/models/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 8c769be0489..1293df571a3 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -52,7 +52,7 @@ module TokenAuthenticatable
mod.define_method("#{token_field}_matches?") do |other_token|
token = read_attribute(token_field)
- token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(other_token, token)
+ token.present? && ActiveSupport::SecurityUtils.secure_compare(other_token, token)
end
end
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
deleted file mode 100644
index d0f5b6970b1..00000000000
--- a/app/models/cycle_analytics.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-class CycleAnalytics
- STAGES = %i[issue plan code test review staging production].freeze
-
- def initialize(project, options)
- @project = project
- @options = options
- end
-
- def all_medians_per_stage
- STAGES.each_with_object({}) do |stage_name, medians_per_stage|
- medians_per_stage[stage_name] = self[stage_name].median
- end
- end
-
- def summary
- @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project,
- from: @options[:from],
- current_user: @options[:current_user]).data
- end
-
- def stats
- @stats ||= stats_per_stage
- end
-
- def no_stats?
- stats.all? { |hash| hash[:value].nil? }
- end
-
- def permissions(user:)
- Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project)
- end
-
- def [](stage_name)
- Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options)
- end
-
- private
-
- def stats_per_stage
- STAGES.map do |stage_name|
- self[stage_name].as_json
- end
- end
-end
diff --git a/app/models/cycle_analytics/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
new file mode 100644
index 00000000000..4aa426c58a1
--- /dev/null
+++ b/app/models/cycle_analytics/project_level.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module CycleAnalytics
+ class ProjectLevel
+ include LevelBase
+ attr_reader :project, :options
+
+ def initialize(project, options:)
+ @project = project
+ @options = options.merge(project: project)
+ end
+
+ def summary
+ @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project,
+ from: options[:from],
+ current_user: options[:current_user]).data
+ end
+
+ def permissions(user:)
+ Gitlab::CycleAnalytics::Permissions.get(user: user, project: project)
+ end
+ end
+end
diff --git a/app/models/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/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/discussion.rb b/app/models/discussion.rb
index ae13cdfd85f..dd896f77084 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -38,6 +38,17 @@ class Discussion
grouped_notes.values.map { |notes| build(notes, context_noteable) }
end
+ def self.lazy_find(discussion_id)
+ BatchLoader.for(discussion_id).batch do |discussion_ids, loader|
+ results = Note.where(discussion_id: discussion_ids).fresh.to_a.group_by(&:discussion_id)
+ results.each do |discussion_id, notes|
+ next if notes.empty?
+
+ loader.call(discussion_id, Discussion.build(notes))
+ end
+ end
+ end
+
# Returns an alphanumeric discussion ID based on `build_discussion_id`
def self.discussion_id(note)
Digest::SHA1.hexdigest(build_discussion_id(note).join("-"))
diff --git a/app/models/email.rb b/app/models/email.rb
index 0ddaa049c3b..580633d3232 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -4,9 +4,8 @@ class Email < ApplicationRecord
include Sortable
include Gitlab::SQL::Pattern
- belongs_to :user
+ belongs_to :user, optional: false
- validates :user_id, presence: true
validates :email, presence: true, uniqueness: true, devise_email: true
validate :unique_email, if: ->(email) { email.email_changed? }
diff --git a/app/models/environment.rb b/app/models/environment.rb
index b8ee54c1696..392481ea0cc 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
@@ -203,40 +198,6 @@ 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
@@ -274,11 +235,7 @@ class Environment < ApplicationRecord
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/environment_status.rb b/app/models/environment_status.rb
index 465a42759df..d7dc64190d6 100644
--- a/app/models/environment_status.rb
+++ b/app/models/environment_status.rb
@@ -40,7 +40,7 @@ class EnvironmentStatus
end
def changes
- return [] if project.route_map_for(sha).nil?
+ return [] unless has_route_map?
changed_files.map { |file| build_change(file) }.compact
end
@@ -50,6 +50,10 @@ class EnvironmentStatus
.merge_request_diff_files.where(deleted_file: false)
end
+ def has_route_map?
+ project.route_map_for(sha).present?
+ end
+
private
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
diff --git a/app/models/group.rb b/app/models/group.rb
index 9520db1bc0a..26ce2957e9b 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -144,6 +144,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
@@ -416,6 +422,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/issue.rb b/app/models/issue.rb
index 982a94315bd..8c5dd5e382e 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -13,11 +13,8 @@ class Issue < ApplicationRecord
include RelativePositioning
include TimeTrackable
include ThrottledTouch
- include IgnorableColumn
include LabelEventable
- ignore_column :assignee_id, :branch_name, :deleted_at
-
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
@@ -134,7 +131,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_note.rb b/app/models/label_note.rb
index d6814f4a948..ba5f1f82a81 100644
--- a/app/models/label_note.rb
+++ b/app/models/label_note.rb
@@ -62,19 +62,27 @@ class LabelNote < Note
end
def note_text(html: false)
- added = labels_str('added', label_refs_by_action('add', html))
- removed = labels_str('removed', label_refs_by_action('remove', html))
+ added = labels_str(label_refs_by_action('add', html), prefix: 'added', suffix: added_suffix)
+ removed = labels_str(label_refs_by_action('remove', html), prefix: removed_prefix)
[added, removed].compact.join(' and ')
end
+ def removed_prefix
+ 'removed'
+ end
+
+ def added_suffix
+ ''
+ end
+
# returns string containing added/removed labels including
# count of deleted labels:
#
# added ~1 ~2 + 1 deleted label
# added 3 deleted labels
# added ~1 ~2 labels
- def labels_str(prefix, label_refs)
+ def labels_str(label_refs, prefix: '', suffix: '')
existing_refs = label_refs.select { |ref| ref.present? }.sort
refs_str = existing_refs.empty? ? nil : existing_refs.join(' ')
@@ -84,9 +92,9 @@ class LabelNote < Note
return unless refs_str || deleted_str
label_list_str = [refs_str, deleted_str].compact.join(' + ')
- suffix = 'label'.pluralize(deleted > 0 ? deleted : existing_refs.count)
+ suffix += ' label'.pluralize(deleted > 0 ? deleted : existing_refs.count)
- "#{prefix} #{label_list_str} #{suffix}"
+ "#{prefix} #{label_list_str} #{suffix.squish}"
end
def label_refs_by_action(action, html)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 53977748c30..68e6e48fb7d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -7,7 +7,6 @@ class MergeRequest < ApplicationRecord
include Noteable
include Referable
include Presentable
- include IgnorableColumn
include TimeTrackable
include ManualInverseAssociation
include EachBatch
@@ -24,10 +23,6 @@ class MergeRequest < ApplicationRecord
SORTING_PREFERENCE_FIELD = :merge_requests_sort
- ignore_column :locked_at,
- :ref_fetched,
- :deleted_at
-
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User"
@@ -588,7 +583,11 @@ class MergeRequest < ApplicationRecord
end
def diff_refs
- persisted? ? merge_request_diff.diff_refs : repository_diff_refs
+ if importing? || persisted?
+ merge_request_diff.diff_refs
+ else
+ repository_diff_refs
+ end
end
# Instead trying to fetch the
diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb
index 22cedf57b86..5c53cfd8c27 100644
--- a/app/models/merge_requests_closing_issues.rb
+++ b/app/models/merge_requests_closing_issues.rb
@@ -25,7 +25,7 @@ class MergeRequestsClosingIssues < ApplicationRecord
class << self
def count_for_collection(ids, current_user)
- closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', 'COUNT(*) as count')
+ closing_merge_requests(ids, current_user).group(:issue_id).pluck('issue_id', Arel.sql('COUNT(*) as count'))
end
def count_for_issue(id, current_user)
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index af50293a179..b8d7348268a 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -8,13 +8,10 @@ class Namespace < ApplicationRecord
include AfterCommitQueue
include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
- include IgnorableColumn
include FeatureGate
include FromUnion
include Gitlab::Utils::StrongMemoize
- ignore_column :deleted_at
-
# Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of
# Android repo (15) + some extra backup.
@@ -41,8 +38,7 @@ class Namespace < ApplicationRecord
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name,
presence: true,
- length: { maximum: 255 },
- namespace_name: true
+ length: { maximum: 255 }
validates :description, length: { maximum: 255 }
validates :path,
@@ -264,6 +260,11 @@ class Namespace < ApplicationRecord
false
end
+ # Overridden in EE::Namespace
+ def feature_available?(_feature)
+ false
+ end
+
def full_path_before_last_save
if parent_id_before_last_save.nil?
path_before_last_save
diff --git a/app/models/note.rb b/app/models/note.rb
index 4e9ea146485..5c31cff9816 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -158,6 +158,8 @@ class Note < ApplicationRecord
Discussion.build_collection(all.includes(:noteable).fresh, context_noteable)
end
+ # Note: Where possible consider using Discussion#lazy_find to return
+ # Discussions in order to benefit from having records batch loaded.
def find_discussion(discussion_id)
notes = where(discussion_id: discussion_id).fresh.to_a
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index d6d879c6d89..27c122d3559 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -12,15 +12,17 @@ class PagesDomain < ApplicationRecord
validates :domain, hostname: { allow_numeric_hostname: true }
validates :domain, uniqueness: { case_sensitive: false }
- validates :certificate, presence: { message: 'must be present if HTTPS-only is enabled' }, if: ->(domain) { domain.project&.pages_https_only? }
+ validates :certificate, presence: { message: 'must be present if HTTPS-only is enabled' },
+ if: :certificate_should_be_present?
validates :certificate, certificate: true, if: ->(domain) { domain.certificate.present? }
- validates :key, presence: { message: 'must be present if HTTPS-only is enabled' }, if: ->(domain) { domain.project&.pages_https_only? }
+ validates :key, presence: { message: 'must be present if HTTPS-only is enabled' },
+ if: :certificate_should_be_present?
validates :key, certificate_key: true, if: ->(domain) { domain.key.present? }
validates :verification_code, presence: true, allow_blank: false
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,
@@ -249,4 +251,8 @@ class PagesDomain < ApplicationRecord
rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError
nil
end
+
+ def certificate_should_be_present?
+ !auto_ssl_enabled? && project&.pages_https_only?
+ end
end
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 0f4fba5d0b6..8030c645e2e 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -15,6 +15,7 @@ class Project < ApplicationRecord
include CaseSensitivity
include TokenAuthenticatable
include ValidAttribute
+ include ProjectAPICompatibility
include ProjectFeaturesCompatibility
include SelectForProjectAuthorization
include Presentable
@@ -30,7 +31,6 @@ class Project < ApplicationRecord
include FeatureGate
include OptionallySearch
include FromUnion
- include IgnorableColumn
extend Gitlab::Cache::RequestCache
extend Gitlab::ConfigHelper
@@ -55,8 +55,6 @@ class Project < ApplicationRecord
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
- ignore_column :import_status, :import_jid, :import_error
-
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
@@ -279,7 +277,7 @@ 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'
@@ -356,9 +354,10 @@ class Project < ApplicationRecord
scope :with_unmigrated_storage, -> { where('storage_version < :version OR storage_version IS NULL', version: LATEST_STORAGE_VERSION) }
# last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push
- scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") }
+ 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) }
@@ -431,7 +430,7 @@ class Project < ApplicationRecord
numericality: { greater_than_or_equal_to: 10.minutes,
less_than: 1.month,
only_integer: true,
- message: _('needs to be beetween 10 minutes and 1 month') }
+ message: _('needs to be between 10 minutes and 1 month') }
# Used by Projects::CleanupService to hold a map of rewritten object IDs
mount_uploader :bfg_object_map, AttachmentUploader
@@ -443,22 +442,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
@@ -611,7 +594,7 @@ class Project < ApplicationRecord
end
end
- def initialize(attributes = {})
+ def initialize(attributes = nil)
# We can't use default_value_for because the database has a default
# value of 0 for visibility_level. If someone attempts to create a
# private project, default_value_for will assume that the
@@ -621,6 +604,8 @@ class Project < ApplicationRecord
#
# To fix the problem, we assign the actual default in the application if
# no explicit visibility has been initialized.
+ attributes ||= {}
+
unless visibility_attribute_present?(attributes)
attributes[:visibility_level] = Gitlab::CurrentSettings.default_project_visibility
end
@@ -687,10 +672,6 @@ class Project < ApplicationRecord
{ scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) }
end
- def multiple_mr_assignees_enabled?
- Feature.enabled?(:multiple_merge_request_assignees, self)
- end
-
def daily_statistics_enabled?
Feature.enabled?(:project_daily_statistics, self, default_enabled: true)
end
@@ -783,6 +764,7 @@ class Project < ApplicationRecord
job_id
end
+ # rubocop:disable Gitlab/RailsLogger
def log_import_activity(job_id, type: :import)
job_type = type.to_s.capitalize
@@ -792,6 +774,7 @@ class Project < ApplicationRecord
Rails.logger.error("#{job_type} job failed to create for #{full_path}.")
end
end
+ # rubocop:enable Gitlab/RailsLogger
def reset_cache_and_import_attrs
run_after_commit do
@@ -1498,12 +1481,20 @@ class Project < ApplicationRecord
!namespace.share_with_group_lock
end
- def pipeline_for(ref, sha = nil)
+ def pipeline_for(ref, sha = nil, id = nil)
+ if id.present?
+ pipelines_for(ref, sha).find_by(id: id)
+ else
+ pipelines_for(ref, sha).take
+ end
+ end
+
+ def pipelines_for(ref, sha = nil)
sha ||= commit(ref).try(:sha)
return unless sha
- ci_pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
+ ci_pipelines.order(id: :desc).where(sha: sha, ref: ref)
end
def latest_successful_pipeline_for_default_branch
@@ -1554,7 +1545,7 @@ class Project < ApplicationRecord
end
def valid_runners_token?(token)
- self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token)
+ self.runners_token && ActiveSupport::SecurityUtils.secure_compare(token, self.runners_token)
end
# rubocop: disable CodeReuse/ServiceClass
@@ -1664,6 +1655,7 @@ class Project < ApplicationRecord
end
# rubocop: enable CodeReuse/ServiceClass
+ # rubocop:disable Gitlab/RailsLogger
def write_repository_config(gl_full_path: full_path)
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
@@ -1673,6 +1665,7 @@ class Project < ApplicationRecord
Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
nil
end
+ # rubocop:enable Gitlab/RailsLogger
def after_import
repository.after_import
@@ -1714,6 +1707,7 @@ class Project < ApplicationRecord
@pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self)
end
+ # rubocop:disable Gitlab/RailsLogger
def add_export_job(current_user:, after_export_strategy: nil, params: {})
job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params)
@@ -1723,6 +1717,7 @@ class Project < ApplicationRecord
Rails.logger.error "Export job failed to start for project ID #{self.id}"
end
end
+ # rubocop:enable Gitlab/RailsLogger
def import_export_shared
@import_export_shared ||= Gitlab::ImportExport::Shared.new(self)
@@ -1913,9 +1908,8 @@ class Project < ApplicationRecord
@route_maps_by_commit ||= Hash.new do |h, sha|
h[sha] = begin
data = repository.route_map_for(sha)
- next unless data
- Gitlab::RouteMap.new(data)
+ Gitlab::RouteMap.new(data) if data
rescue Gitlab::RouteMap::FormatError
nil
end
@@ -2144,7 +2138,7 @@ class Project < ApplicationRecord
public? &&
repository_exists? &&
Gitlab::CurrentSettings.hashed_storage_enabled &&
- Feature.enabled?(:object_pools, self)
+ Feature.enabled?(:object_pools, self, default_enabled: true)
end
def leave_pool_repository
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 67c12363a3c..f39f54f0434 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -5,7 +5,7 @@ class ProjectAutoDevops < ApplicationRecord
ignore_column :domain
- belongs_to :project
+ belongs_to :project, inverse_of: :auto_devops
enum deploy_strategy: {
continuous: 0,
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 0542581c6e0..78e82955342 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -24,6 +24,12 @@ class ProjectFeature < ApplicationRecord
FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL = { merge_requests: Gitlab::Access::REPORTER }.freeze
+ STRING_OPTIONS = HashWithIndifferentAccess.new({
+ 'disabled' => DISABLED,
+ 'private' => PRIVATE,
+ 'enabled' => ENABLED,
+ 'public' => PUBLIC
+ }).freeze
class << self
def access_level_attribute(feature)
@@ -45,6 +51,14 @@ class ProjectFeature < ApplicationRecord
PRIVATE_FEATURES_MIN_ACCESS_LEVEL.fetch(feature, Gitlab::Access::GUEST)
end
+ def access_level_from_str(level)
+ STRING_OPTIONS.fetch(level)
+ end
+
+ def str_from_access_level(level)
+ STRING_OPTIONS.key(level)
+ end
+
private
def ensure_feature!(feature)
@@ -72,6 +86,8 @@ class ProjectFeature < ApplicationRecord
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
+ default_value_for(:pages_access_level, allows_nil: false) { |feature| feature.project&.public? ? ENABLED : PRIVATE }
+
def feature_available?(feature, user)
# This feature might not be behind a feature flag at all, so default to true
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
@@ -83,6 +99,10 @@ class ProjectFeature < ApplicationRecord
public_send(ProjectFeature.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
end
+ def string_access_level(feature)
+ ProjectFeature.str_from_access_level(access_level(feature))
+ end
+
def builds_enabled?
builds_access_level > DISABLED
end
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index 1605345efd5..23adffb33d8 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -65,7 +65,7 @@ class ProjectImportState < ApplicationRecord
update_column(:last_error, sanitized_message)
rescue ActiveRecord::ActiveRecordError => e
- Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
+ Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}") # rubocop:disable Gitlab/RailsLogger
ensure
@errors = original_errors
end
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index f0ef2d925ab..47106d7bdbb 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -7,7 +7,7 @@ class CiService < Service
default_value_for :category, 'ci'
def valid_token?(token)
- self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
+ self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.secure_compare(token, self.token)
end
def self.supported_events
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index a3b89b2543a..7ab79242cc3 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -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.
@@ -250,7 +256,7 @@ class JiraService < IssueTrackerService
end
log_info("Successfully posted", client_url: client_url)
- "SUCCESS: Successfully posted to http://jira.example.net."
+ "SUCCESS: Successfully posted to #{client_url}."
end
end
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index bfabc6d262c..5f5cff97808 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -12,7 +12,7 @@ class SlashCommandsService < Service
def valid_token?(token)
self.respond_to?(:token) &&
self.token.present? &&
- ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
+ ActiveSupport::SecurityUtils.secure_compare(token, self.token)
end
def self.supported_events
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index 62090444f79..b8e7673dcf5 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -3,68 +3,7 @@
class PrometheusMetric < ApplicationRecord
belongs_to :project, validate: true, inverse_of: :prometheus_metrics
- enum group: {
- # built-in groups
- nginx_ingress_vts: -1,
- ha_proxy: -2,
- aws_elb: -3,
- nginx: -4,
- kubernetes: -5,
- nginx_ingress: -6,
-
- # custom/user groups
- business: 0,
- response: 1,
- system: 2
- }
-
- GROUP_DETAILS = {
- # built-in groups
- nginx_ingress_vts: {
- group_title: _('Response metrics (NGINX Ingress VTS)'),
- required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
- priority: 10
- }.freeze,
- nginx_ingress: {
- group_title: _('Response metrics (NGINX Ingress)'),
- required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum),
- priority: 10
- }.freeze,
- ha_proxy: {
- group_title: _('Response metrics (HA Proxy)'),
- required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
- priority: 10
- }.freeze,
- aws_elb: {
- group_title: _('Response metrics (AWS ELB)'),
- required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
- priority: 10
- }.freeze,
- nginx: {
- group_title: _('Response metrics (NGINX)'),
- required_metrics: %w(nginx_server_requests nginx_server_requestMsec),
- priority: 10
- }.freeze,
- kubernetes: {
- group_title: _('System metrics (Kubernetes)'),
- required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
- priority: 5
- }.freeze,
-
- # custom/user groups
- business: {
- group_title: _('Business metrics (Custom)'),
- priority: 0
- }.freeze,
- response: {
- group_title: _('Response metrics (Custom)'),
- priority: -5
- }.freeze,
- system: {
- group_title: _('System metrics (Custom)'),
- priority: -10
- }.freeze
- }.freeze
+ enum group: PrometheusMetricEnums.groups
validates :title, presence: true
validates :query, presence: true
@@ -121,6 +60,6 @@ class PrometheusMetric < ApplicationRecord
private
def group_details(group)
- GROUP_DETAILS.fetch(group.to_sym)
+ PrometheusMetricEnums.group_details.fetch(group.to_sym)
end
end
diff --git a/app/models/prometheus_metric_enums.rb b/app/models/prometheus_metric_enums.rb
new file mode 100644
index 00000000000..6cb22cc69cd
--- /dev/null
+++ b/app/models/prometheus_metric_enums.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module PrometheusMetricEnums
+ def self.groups
+ {
+ # built-in groups
+ nginx_ingress_vts: -1,
+ ha_proxy: -2,
+ aws_elb: -3,
+ nginx: -4,
+ kubernetes: -5,
+ nginx_ingress: -6,
+
+ # custom/user groups
+ business: 0,
+ response: 1,
+ system: 2
+ }
+ end
+
+ def self.group_details
+ {
+ # built-in groups
+ nginx_ingress_vts: {
+ group_title: _('Response metrics (NGINX Ingress VTS)'),
+ required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
+ priority: 10
+ }.freeze,
+ nginx_ingress: {
+ group_title: _('Response metrics (NGINX Ingress)'),
+ required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum),
+ priority: 10
+ }.freeze,
+ ha_proxy: {
+ group_title: _('Response metrics (HA Proxy)'),
+ required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
+ priority: 10
+ }.freeze,
+ aws_elb: {
+ group_title: _('Response metrics (AWS ELB)'),
+ required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
+ priority: 10
+ }.freeze,
+ nginx: {
+ group_title: _('Response metrics (NGINX)'),
+ required_metrics: %w(nginx_server_requests nginx_server_requestMsec),
+ priority: 10
+ }.freeze,
+ kubernetes: {
+ group_title: _('System metrics (Kubernetes)'),
+ required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
+ priority: 5
+ }.freeze,
+
+ # custom/user groups
+ business: {
+ group_title: _('Business metrics (Custom)'),
+ priority: 0
+ }.freeze,
+ response: {
+ group_title: _('Response metrics (Custom)'),
+ priority: -5
+ }.freeze,
+ system: {
+ group_title: _('System metrics (Custom)'),
+ priority: -10
+ }.freeze
+ }.freeze
+ end
+end
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index af705b29f7a..6b5605f9999 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -31,7 +31,7 @@ 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, -> { started.where('last_update_at < ? OR (last_update_at IS NULL AND updated_at < ?)', 1.hour.ago, 3.hours.ago) }
state_machine :update_status, initial: :none do
event :update_start do
@@ -173,7 +173,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
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a25d5abfa64..187382ad182 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -273,7 +273,7 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
raw_repository.write_ref(keep_around_ref_name(sha), sha)
rescue Gitlab::Git::CommandError => ex
- Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
+ Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" # rubocop:disable Gitlab/RailsLogger
end
end
@@ -934,6 +934,7 @@ class Repository
async_remove_remote(remote_name) if tmp_remote_name
end
+ # rubocop:disable Gitlab/RailsLogger
def async_remove_remote(remote_name)
return unless remote_name
@@ -947,6 +948,7 @@ class Repository
job_id
end
+ # rubocop:enable Gitlab/RailsLogger
def fetch_source_branch!(source_repository, source_branch, local_ref)
raw_repository.fetch_source_branch!(source_repository.raw_repository, source_branch, local_ref)
diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb
index f2c7cb6a65d..ad08f4763ae 100644
--- a/app/models/resource_label_event.rb
+++ b/app/models/resource_label_event.rb
@@ -36,10 +36,9 @@ class ResourceLabelEvent < ApplicationRecord
issue || merge_request
end
- # create same discussion id for all actions with the same user and time
def discussion_id(resource = nil)
strong_memoize(:discussion_id) do
- Digest::SHA1.hexdigest([self.class.name, created_at, user_id].join("-"))
+ Digest::SHA1.hexdigest(discussion_id_key.join("-"))
end
end
@@ -121,4 +120,8 @@ class ResourceLabelEvent < ApplicationRecord
def resource_parent
issuable.project || issuable.group
end
+
+ def discussion_id_key
+ [self.class.name, created_at, user_id]
+ end
end
diff --git a/app/models/ssh_host_key.rb b/app/models/ssh_host_key.rb
index b6fb39ee81f..9bd35d30845 100644
--- a/app/models/ssh_host_key.rb
+++ b/app/models/ssh_host_key.rb
@@ -106,7 +106,7 @@ class SshHostKey
if status.success? && !errors.present?
{ known_hosts: known_hosts }
else
- Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}")
+ Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}") # rubocop:disable Gitlab/RailsLogger
{ error: 'Failed to detect SSH host keys' }
end
diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb
index b483c677be9..928c773c307 100644
--- a/app/models/storage/legacy_project.rb
+++ b/app/models/storage/legacy_project.rb
@@ -41,7 +41,7 @@ module Storage
gitlab_shell.mv_repository(repository_storage, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
return true
rescue => e
- Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}"
+ Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}" # rubocop:disable Gitlab/RailsLogger
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
return false
diff --git a/app/models/uploads/base.rb b/app/models/uploads/base.rb
index f9814159958..29f376670da 100644
--- a/app/models/uploads/base.rb
+++ b/app/models/uploads/base.rb
@@ -7,7 +7,7 @@ module Uploads
attr_reader :logger
def initialize(logger: nil)
- @logger ||= Rails.logger
+ @logger ||= Rails.logger # rubocop:disable Gitlab/RailsLogger
end
def delete_keys_async(keys_to_delete)
diff --git a/app/models/user.rb b/app/models/user.rb
index 26be197209a..b439d1c0c16 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -185,6 +185,7 @@ class User < ApplicationRecord
before_validation :set_notification_email, if: :new_record?
before_validation :set_public_email, if: :public_email_changed?
before_validation :set_commit_email, if: :commit_email_changed?
+ before_save :default_private_profile_to_false
before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
before_save :set_commit_email, if: :commit_email_changed? # in case validation is skipped
before_save :ensure_incoming_email_token
@@ -1117,9 +1118,10 @@ class User < ApplicationRecord
def ensure_namespace_correct
if namespace
- namespace.path = namespace.name = username if username_changed?
+ namespace.path = username if username_changed?
+ namespace.name = name if name_changed?
else
- build_namespace(path: username, name: username)
+ build_namespace(path: username, name: name)
end
end
@@ -1257,6 +1259,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 +1497,12 @@ class User < ApplicationRecord
private
+ def default_private_profile_to_false
+ return unless private_profile_changed? && private_profile.nil?
+
+ self.private_profile = false
+ end
+
def has_current_license?
false
end
diff --git a/app/policies/ci/trigger_policy.rb b/app/policies/ci/trigger_policy.rb
index 209db44539c..578301d7f7e 100644
--- a/app/policies/ci/trigger_policy.rb
+++ b/app/policies/ci/trigger_policy.rb
@@ -5,7 +5,7 @@ module Ci
delegate { @subject.project }
with_options scope: :subject, score: 0
- condition(:legacy) { @subject.legacy? }
+ condition(:legacy) { @subject.supports_legacy_tokens? && @subject.legacy? }
with_score 0
condition(:is_owner) { @user && @subject.owner_id == @user.id }
diff --git a/app/policies/clusters/instance_policy.rb b/app/policies/clusters/instance_policy.rb
index f72096e8fc6..bd7ff413afe 100644
--- a/app/policies/clusters/instance_policy.rb
+++ b/app/policies/clusters/instance_policy.rb
@@ -2,11 +2,6 @@
module Clusters
class InstancePolicy < BasePolicy
- include ClusterableActions
-
- condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
- condition(:can_have_multiple_clusters) { multiple_clusters_available? }
-
rule { admin }.policy do
enable :read_cluster
enable :add_cluster
@@ -14,7 +9,5 @@ module Clusters
enable :update_cluster
enable :admin_cluster
end
-
- rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
end
end
diff --git a/app/policies/concerns/clusterable_actions.rb b/app/policies/concerns/clusterable_actions.rb
deleted file mode 100644
index 08ddd742ea9..00000000000
--- a/app/policies/concerns/clusterable_actions.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module ClusterableActions
- private
-
- # Overridden on EE module
- def multiple_clusters_available?
- false
- end
-
- def clusterable_has_clusters?
- !subject.clusters.empty?
- end
-end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index ea86858181d..0add8bfad31 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -1,8 +1,6 @@
# frozen_string_literal: true
class GroupPolicy < BasePolicy
- include ClusterableActions
-
desc "Group is public"
with_options scope: :subject, score: 0
condition(:public_group) { @subject.public? }
@@ -29,9 +27,6 @@ class GroupPolicy < BasePolicy
GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any?
end
- condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
- condition(:can_have_multiple_clusters) { multiple_clusters_available? }
-
with_options scope: :subject, score: 0
condition(:request_access_enabled) { @subject.request_access_enabled }
@@ -43,6 +38,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
@@ -110,6 +109,7 @@ class GroupPolicy < BasePolicy
end
rule { owner & nested_groups_supported }.enable :create_subgroup
+ rule { maintainer & maintainer_can_create_group & nested_groups_supported }.enable :create_subgroup
rule { public_group | logged_in_viewable }.enable :view_globally
@@ -121,8 +121,6 @@ class GroupPolicy < BasePolicy
rule { owner & (~share_with_group_locked | ~has_parent | ~parent_share_with_group_locked | can_change_parent_share_with_group_lock) }.enable :change_share_with_group_lock
- rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
-
rule { developer & developer_maintainer_access }.enable :create_projects
rule { create_projects_disabled }.prevent :create_projects
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 3c9ffbb2065..e79bac6bee3 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -2,7 +2,6 @@
class ProjectPolicy < BasePolicy
extend ClassMethods
- include ClusterableActions
READONLY_FEATURES_WHEN_ARCHIVED = %i[
issue
@@ -114,9 +113,6 @@ class ProjectPolicy < BasePolicy
@subject.feature_available?(:merge_requests, @user)
end
- condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
- condition(:can_have_multiple_clusters) { multiple_clusters_available? }
-
condition(:internal_builds_disabled) do
!@subject.builds_enabled?
end
@@ -430,8 +426,6 @@ class ProjectPolicy < BasePolicy
(~guest & can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request)
end.enable :read_merge_request_iid
- rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
-
rule { ~can?(:read_cross_project) & ~classification_label_authorized }.policy do
# Preventing access here still allows the projects to be listed. Listing
# projects doesn't check the `:read_project` ability. But instead counts
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..21a1e1309e0 100644
--- a/app/presenters/blobs/unfold_presenter.rb
+++ b/app/presenters/blobs/unfold_presenter.rb
@@ -16,8 +16,12 @@ module Blobs
attribute :indent, Integer, default: 0
def initialize(blob, params)
+ # Load all blob data first as we need to ensure they're all loaded first
+ # so we can accurately show the rest of the diff when unfolding.
+ load_all_blob_data
+
@subject = blob
- @all_lines = highlight.lines
+ @all_lines = blob.data.lines
super(params)
if full?
@@ -25,10 +29,12 @@ module Blobs
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)
@@ -37,11 +43,7 @@ module Blobs
end
def lines
- strong_memoize(:lines) do
- lines = @all_lines
- lines = lines[since - 1..to - 1] unless full?
- lines.map(&:html_safe)
- end
+ @lines ||= limit(highlight.lines).map(&:html_safe)
end
def match_line_text
@@ -71,5 +73,15 @@ module Blobs
bottom? ? diff_lines.push(match_line) : diff_lines.unshift(match_line)
end
+
+ def limited_blob_lines
+ @limited_blob_lines ||= limit(@all_lines)
+ end
+
+ def limit(lines)
+ return lines if full?
+
+ lines[since - 1..to - 1]
+ end
end
end
diff --git a/app/presenters/clusterable_presenter.rb b/app/presenters/clusterable_presenter.rb
index 34bdf156623..fff6d23efdf 100644
--- a/app/presenters/clusterable_presenter.rb
+++ b/app/presenters/clusterable_presenter.rb
@@ -13,7 +13,8 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
end
def can_add_cluster?
- can?(current_user, :add_cluster, clusterable)
+ can?(current_user, :add_cluster, clusterable) &&
+ (has_no_clusters? || multiple_clusters_available?)
end
def can_create_cluster?
@@ -63,4 +64,15 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
def learn_more_link
raise NotImplementedError
end
+
+ private
+
+ # Overridden on EE module
+ def multiple_clusters_available?
+ false
+ end
+
+ def has_no_clusters?
+ clusterable.clusters.empty?
+ end
end
diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb
index ab15bd0ac7a..29d4a6ae1d0 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[:path], object[:name], 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/current_board_entity.rb b/app/serializers/current_board_entity.rb
new file mode 100644
index 00000000000..371151532f8
--- /dev/null
+++ b/app/serializers/current_board_entity.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class CurrentBoardEntity < Grape::Entity
+ expose :id
+ expose :name
+end
diff --git a/app/serializers/current_board_serializer.rb b/app/serializers/current_board_serializer.rb
new file mode 100644
index 00000000000..c58c77194f2
--- /dev/null
+++ b/app/serializers/current_board_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class CurrentBoardSerializer < BaseSerializer
+ entity CurrentBoardEntity
+end
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
index d8630165e49..ee68b4b98e0 100644
--- a/app/serializers/diff_file_base_entity.rb
+++ b/app/serializers/diff_file_base_entity.rb
@@ -3,7 +3,6 @@
class DiffFileBaseEntity < Grape::Entity
include RequestAwareEntity
include BlobHelper
- include SubmoduleHelper
include DiffHelper
include TreeHelper
include ChecksCollaboration
@@ -12,12 +11,12 @@ class DiffFileBaseEntity < Grape::Entity
expose :content_sha
expose :submodule?, as: :submodule
- expose :submodule_link do |diff_file|
- memoized_submodule_links(diff_file).first
+ expose :submodule_link do |diff_file, options|
+ memoized_submodule_links(diff_file, options).first
end
expose :submodule_tree_url do |diff_file|
- memoized_submodule_links(diff_file).last
+ memoized_submodule_links(diff_file, options).last
end
expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
@@ -92,10 +91,10 @@ class DiffFileBaseEntity < Grape::Entity
private
- def memoized_submodule_links(diff_file)
+ def memoized_submodule_links(diff_file, options)
strong_memoize(:submodule_links) do
if diff_file.submodule?
- submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository)
+ options[:submodule_links].for(diff_file.blob, diff_file.content_sha)
else
[]
end
diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb
index b51e4a7e6d0..1763fe5b6ab 100644
--- a/app/serializers/diffs_entity.rb
+++ b/app/serializers/diffs_entity.rb
@@ -64,7 +64,10 @@ class DiffsEntity < Grape::Entity
merge_request_path(merge_request, format: :diff)
end
- expose :diff_files, using: DiffFileEntity
+ expose :diff_files do |diffs, options|
+ submodule_links = Gitlab::SubmoduleLinks.new(merge_request.project.repository)
+ DiffFileEntity.represent(diffs.diff_files, options.merge(submodule_links: submodule_links))
+ end
expose :merge_request_diffs, using: MergeRequestDiffEntity, if: -> (_, options) { options[:merge_request_diffs]&.any? } do |diffs|
options[:merge_request_diffs]
diff --git a/app/serializers/discussion_serializer.rb b/app/serializers/discussion_serializer.rb
index 5be40e74175..8bb7e93c033 100644
--- a/app/serializers/discussion_serializer.rb
+++ b/app/serializers/discussion_serializer.rb
@@ -2,4 +2,18 @@
class DiscussionSerializer < BaseSerializer
entity DiscussionEntity
+
+ def represent(resource, opts = {}, entity_class = nil)
+ super(resource, with_additional_opts(opts), entity_class)
+ end
+
+ private
+
+ def with_additional_opts(opts)
+ additional_opts = {
+ submodule_links: Gitlab::SubmoduleLinks.new(@request.project.repository)
+ }
+
+ opts.merge(additional_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/submodule_entity.rb b/app/serializers/submodule_entity.rb
deleted file mode 100644
index e475a4f301f..00000000000
--- a/app/serializers/submodule_entity.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-class SubmoduleEntity < Grape::Entity
- include RequestAwareEntity
-
- expose :id, :path, :name, :mode
-
- expose :icon do |blob|
- 'archive'
- end
-
- expose :url do |blob|
- submodule_links(blob, request).first
- end
-
- expose :tree_url do |blob|
- submodule_links(blob, request).last
- end
-
- private
-
- def submodule_links(blob, request)
- @submodule_links ||= SubmoduleHelper.submodule_links(blob, request.ref, request.repository)
- end
-end
diff --git a/app/services/akismet_service.rb b/app/services/akismet_service.rb
index 82ae66ab0f5..63be3c371ec 100644
--- a/app/services/akismet_service.rb
+++ b/app/services/akismet_service.rb
@@ -25,7 +25,7 @@ class AkismetService
is_spam, is_blatant = akismet_client.check(options[:ip_address], options[:user_agent], params)
is_spam || is_blatant
rescue => e
- Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check") # rubocop:disable Gitlab/RailsLogger
false
end
end
@@ -63,7 +63,7 @@ class AkismetService
akismet_client.public_send(type, options[:ip_address], options[:user_agent], params) # rubocop:disable GitlabSecurity/PublicSend
true
rescue => e
- Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") # rubocop:disable Gitlab/RailsLogger
false
end
end
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
index 201048aaba5..73f3408a240 100644
--- a/app/services/audit_event_service.rb
+++ b/app/services/audit_event_service.rb
@@ -35,8 +35,12 @@ class AuditEventService
@file_logger ||= Gitlab::AuditJsonLogger.build
end
+ def formatted_details
+ @details.merge(@details.slice(:from, :to).transform_values(&:to_s))
+ end
+
def log_security_event_to_file
- file_logger.info(base_payload.merge(@details))
+ file_logger.info(base_payload.merge(formatted_details))
end
def log_security_event_to_database
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index e27d34dbcab..00ce27db7c8 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -4,14 +4,62 @@ module Boards
module Issues
class MoveService < Boards::BaseService
def execute(issue)
- return false unless can?(current_user, :update_issue, issue)
- return false if issue_params(issue).empty?
+ issue_modification_params = issue_params(issue)
+ return false if issue_modification_params.empty?
- update(issue)
+ move_single_issue(issue, issue_modification_params)
+ end
+
+ def execute_multiple(issues)
+ return execute_multiple_empty_result if issues.empty?
+
+ handled_issues = []
+ last_inserted_issue_id = nil
+ count = issues.each.inject(0) do |moved_count, issue|
+ issue_modification_params = issue_params(issue)
+ next moved_count if issue_modification_params.empty?
+
+ if last_inserted_issue_id
+ issue_modification_params[:move_between_ids] = move_below(last_inserted_issue_id)
+ end
+
+ last_inserted_issue_id = issue.id
+ handled_issue = move_single_issue(issue, issue_modification_params)
+ handled_issues << present_issue_entity(handled_issue) if handled_issue
+ handled_issue && handled_issue.valid? ? moved_count + 1 : moved_count
+ end
+
+ {
+ count: count,
+ success: count == issues.size,
+ issues: handled_issues
+ }
end
private
+ def present_issue_entity(issue)
+ ::API::Entities::Issue.represent(issue)
+ end
+
+ def execute_multiple_empty_result
+ @execute_multiple_empty_result ||= {
+ count: 0,
+ success: false,
+ issues: []
+ }
+ end
+
+ def move_below(id)
+ move_between_ids({ move_after_id: nil, move_before_id: id })
+ end
+
+ def move_single_issue(issue, issue_modification_params)
+ return unless can?(current_user, :update_issue, issue)
+
+ update(issue, issue_modification_params)
+ end
+
def board
@board ||= parent.boards.find(params[:board_id])
end
@@ -33,8 +81,8 @@ module Boards
end
# rubocop: enable CodeReuse/ActiveRecord
- def update(issue)
- ::Issues::UpdateService.new(issue.project, current_user, issue_params(issue)).execute(issue)
+ def update(issue, issue_modification_params)
+ ::Issues::UpdateService.new(issue.project, current_user, issue_modification_params).execute(issue)
end
def issue_params(issue)
@@ -48,6 +96,7 @@ module Boards
)
end
+ move_between_ids = move_between_ids(params)
if move_between_ids
attrs[:move_between_ids] = move_between_ids
attrs[:board_group_id] = board.group&.id
@@ -78,8 +127,8 @@ module Boards
end
# rubocop: enable CodeReuse/ActiveRecord
- def move_between_ids
- ids = [params[:move_after_id], params[:move_before_id]]
+ def move_between_ids(move_params)
+ ids = [move_params[:move_after_id], move_params[:move_before_id]]
.map(&:to_i)
.map { |m| m.positive? ? m : nil }
diff --git a/app/services/ci/archive_trace_service.rb b/app/services/ci/archive_trace_service.rb
index a1dd00721b5..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,13 +39,16 @@ 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}"
+
+ Sidekiq.logger.warn(class: worker_name,
+ message: "Failed to archive trace. message: #{error.message}.",
+ job_id: job.id)
Gitlab::Sentry
.track_exception(error,
- issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
extra: { job_id: job.id })
end
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 4a7ce00b8e2..aaf56048b5c 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -44,7 +44,7 @@ module Ci
# rubocop: disable CodeReuse/ActiveRecord
def stage_indexes_of_created_processables
- created_processables.order(:stage_idx).pluck('distinct stage_idx')
+ created_processables.order(:stage_idx).pluck(Arel.sql('DISTINCT stage_idx'))
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -68,7 +68,7 @@ module Ci
latest_statuses = pipeline.statuses.latest
.group(:name)
.having('count(*) > 1')
- .pluck('max(id)', 'name')
+ .pluck(Arel.sql('MAX(id)'), 'name')
# mark builds that are retried
pipeline.statuses.latest
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index dedab98b56d..9d4cf5df713 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -6,7 +6,7 @@ module Ci
class RegisterJobService
attr_reader :runner
- JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300].freeze
+ JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300, 900].freeze
JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
Result = Struct.new(:build, :valid?)
diff --git a/app/services/clusters/applications/check_uninstall_progress_service.rb b/app/services/clusters/applications/check_uninstall_progress_service.rb
index 8786d295d6a..e51d84ef052 100644
--- a/app/services/clusters/applications/check_uninstall_progress_service.rb
+++ b/app/services/clusters/applications/check_uninstall_progress_service.rb
@@ -23,6 +23,7 @@ module Clusters
private
def on_success
+ app.post_uninstall
app.destroy!
rescue StandardError => e
app.make_errored!(_('Application uninstalled but failed to destroy: %{error_message}') % { error_message: e.message })
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index 886e484caaf..5fb5e15c32d 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -10,24 +10,27 @@ module Clusters
def execute(access_token: nil)
raise ArgumentError, 'Unknown clusterable provided' unless clusterable
- raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?
cluster_params = params.merge(user: current_user).merge(clusterable_params)
cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token
end
- create_cluster(cluster_params).tap do |cluster|
- ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
+ cluster = Clusters::Cluster.new(cluster_params)
+
+ unless can_create_cluster?
+ cluster.errors.add(:base, _('Instance does not support multiple Kubernetes clusters'))
end
- end
- private
+ return cluster if cluster.errors.present?
- def create_cluster(cluster_params)
- Clusters::Cluster.create(cluster_params)
+ cluster.tap do |cluster|
+ cluster.save && ClusterProvisionWorker.perform_async(cluster.id)
+ end
end
+ private
+
def clusterable
@clusterable ||= params.delete(:clusterable)
end
diff --git a/app/services/clusters/gcp/kubernetes.rb b/app/services/clusters/gcp/kubernetes.rb
index 90ed529670c..85711764785 100644
--- a/app/services/clusters/gcp/kubernetes.rb
+++ b/app/services/clusters/gcp/kubernetes.rb
@@ -9,6 +9,8 @@ module Clusters
GITLAB_CLUSTER_ROLE_BINDING_NAME = 'gitlab-admin'
GITLAB_CLUSTER_ROLE_NAME = 'cluster-admin'
PROJECT_CLUSTER_ROLE_NAME = 'edit'
+ GITLAB_KNATIVE_SERVING_ROLE_NAME = 'gitlab-knative-serving-role'
+ GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME = 'gitlab-knative-serving-rolebinding'
end
end
end
diff --git a/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb
index 49e766cbf13..7c5450dbcd6 100644
--- a/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb
+++ b/app/services/clusters/gcp/kubernetes/create_or_update_service_account_service.rb
@@ -41,7 +41,15 @@ module Clusters
kubeclient.create_or_update_service_account(service_account_resource)
kubeclient.create_or_update_secret(service_account_token_resource)
- create_role_or_cluster_role_binding if rbac
+
+ return unless rbac
+
+ create_role_or_cluster_role_binding
+
+ return unless namespace_creator
+
+ create_or_update_knative_serving_role
+ create_or_update_knative_serving_role_binding
end
private
@@ -63,6 +71,14 @@ module Clusters
end
end
+ def create_or_update_knative_serving_role
+ kubeclient.update_role(knative_serving_role_resource)
+ end
+
+ def create_or_update_knative_serving_role_binding
+ kubeclient.update_role_binding(knative_serving_role_binding_resource)
+ end
+
def service_account_resource
Gitlab::Kubernetes::ServiceAccount.new(
service_account_name,
@@ -92,6 +108,29 @@ module Clusters
Gitlab::Kubernetes::RoleBinding.new(
name: role_binding_name,
role_name: Clusters::Gcp::Kubernetes::PROJECT_CLUSTER_ROLE_NAME,
+ role_kind: :ClusterRole,
+ namespace: service_account_namespace,
+ service_account_name: service_account_name
+ ).generate
+ end
+
+ def knative_serving_role_resource
+ Gitlab::Kubernetes::Role.new(
+ name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
+ namespace: service_account_namespace,
+ rules: [{
+ apiGroups: %w(serving.knative.dev),
+ resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services),
+ verbs: %w(get list create update delete patch watch)
+ }]
+ ).generate
+ end
+
+ def knative_serving_role_binding_resource
+ Gitlab::Kubernetes::RoleBinding.new(
+ name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME,
+ role_name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
+ role_kind: :Role,
namespace: service_account_namespace,
service_account_name: service_account_name
).generate
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/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb
index 2cb73555d85..0c5ecca3a50 100644
--- a/app/services/concerns/exclusive_lease_guard.rb
+++ b/app/services/concerns/exclusive_lease_guard.rb
@@ -58,6 +58,6 @@ module ExclusiveLeaseGuard
end
def log_error(message, extra_args = {})
- Rails.logger.error(message)
+ Rails.logger.error(message) # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/services/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/groups/create_service.rb b/app/services/groups/create_service.rb
index e9659f5489a..e78e5d5fc2c 100644
--- a/app/services/groups/create_service.rb
+++ b/app/services/groups/create_service.rb
@@ -27,8 +27,7 @@ module Groups
@group.build_chat_team(name: response['name'], team_id: response['id'])
end
- @group.save
- @group.add_owner(current_user)
+ @group.add_owner(current_user) if @group.save
@group
end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 654fe84e3dc..9e00cbbbc55 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -6,7 +6,7 @@ module Groups
def async_execute
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
- Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
+ Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}") # rubocop:disable Gitlab/RailsLogger
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
index c4beddf2294..6d215d7a3b9 100644
--- a/app/services/issuable/bulk_update_service.rb
+++ b/app/services/issuable/bulk_update_service.rb
@@ -1,7 +1,15 @@
# frozen_string_literal: true
module Issuable
- class BulkUpdateService < IssuableBaseService
+ class BulkUpdateService
+ include Gitlab::Allowable
+
+ attr_accessor :current_user, :params
+
+ def initialize(user = nil, params = {})
+ @current_user, @params = user, params.dup
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def execute(type)
model_class = type.classify.constantize
diff --git a/app/services/issuable/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/clone/content_rewriter.rb b/app/services/issuable/clone/content_rewriter.rb
index 00d7078859d..f75b51c4be3 100644
--- a/app/services/issuable/clone/content_rewriter.rb
+++ b/app/services/issuable/clone/content_rewriter.rb
@@ -23,10 +23,14 @@ module Issuable
end
def rewrite_notes
+ new_discussion_ids = {}
original_entity.notes_with_associations.find_each do |note|
new_note = note.dup
+ new_discussion_ids[note.discussion_id] ||= Discussion.discussion_id(new_note)
new_params = {
- project: new_entity.project, noteable: new_entity,
+ project: new_entity.project,
+ noteable: new_entity,
+ discussion_id: new_discussion_ids[note.discussion_id],
note: rewrite_content(new_note.note),
note_html: nil,
created_at: note.created_at,
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 02de080e0ba..db673cace81 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -182,7 +182,7 @@ class IssuableBaseService < BaseService
# To be overridden by subclasses
end
- def before_update(issuable)
+ def before_update(issuable, skip_spam_check: false)
# To be overridden by subclasses
end
@@ -257,7 +257,7 @@ class IssuableBaseService < BaseService
last_edited_at: Time.now,
last_edited_by: current_user))
- before_update(issuable)
+ before_update(issuable, skip_spam_check: true)
if issuable.with_transaction_returning_status { issuable.save }
# We do not touch as it will affect a update on updated_at field
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 6b9f23f24cd..7cd825aa967 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -17,8 +17,8 @@ module Issues
super
end
- def before_update(issue)
- spam_check(issue, current_user)
+ def before_update(issue, skip_spam_check: false)
+ spam_check(issue, current_user) unless skip_spam_check
end
def handle_changes(issue, options)
diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb
index db710bac900..c032985be42 100644
--- a/app/services/labels/create_service.rb
+++ b/app/services/labels/create_service.rb
@@ -20,7 +20,7 @@ module Labels
label.save
label
else
- Rails.logger.warn("target_params should contain :project or :group or :template, actual value: #{target_params}")
+ Rails.logger.warn("target_params should contain :project or :group or :template, actual value: #{target_params}") # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
index 095bdca5472..1ed396cee1e 100644
--- a/app/services/merge_requests/merge_base_service.rb
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -28,6 +28,17 @@ module MergeRequests
private
+ def check_source
+ unless source
+ raise_error('No source for merge')
+ end
+ end
+
+ # Overridden in EE.
+ def check_size_limit
+ # No-op
+ end
+
# Overridden in EE.
def error_check!
# No-op
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index d8a78001b79..6309052244d 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -48,13 +48,13 @@ module MergeRequests
def error_check!
super
+ check_source
+
error =
if @merge_request.should_be_rebased?
'Only fast-forward merge is allowed for your project. Please update your source branch'
elsif !@merge_request.mergeable?
'Merge request is not mergeable'
- elsif !source
- 'No source for merge'
end
raise_error(error) if error
@@ -113,12 +113,12 @@ module MergeRequests
end
def handle_merge_error(log_message:, save_message_on_model: false)
- Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}")
+ Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}") # rubocop:disable Gitlab/RailsLogger
@merge_request.update(merge_error: log_message) if save_message_on_model
end
def log_info(message)
- @logger ||= Rails.logger
+ @logger ||= Rails.logger # rubocop:disable Gitlab/RailsLogger
@logger.info("#{merge_request_info} - #{message}")
end
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
index 0ea50a5dbf5..37b5805ae7e 100644
--- a/app/services/merge_requests/merge_to_ref_service.rb
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -16,7 +16,7 @@ module MergeRequests
def execute(merge_request)
@merge_request = merge_request
- validate!
+ error_check!
commit_id = commit
@@ -39,21 +39,9 @@ module MergeRequests
merge_request.diff_head_sha
end
- def validate!
- error_check!
- end
-
+ override :error_check!
def error_check!
- super
-
- error =
- if !hooks_validation_pass?(merge_request)
- hooks_validation_error(merge_request)
- elsif source.blank?
- 'No source for merge'
- end
-
- raise_error(error) if error
+ check_source
end
##
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index a24163331e8..6d70b5106c7 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -117,14 +117,8 @@ module MergeRequests
collect_errors_from_merge_request(merge_request) unless merge_request.valid?
end
- def create_params(branch)
- params = {
- assignees: [current_user],
- source_branch: branch,
- source_project: project,
- target_branch: push_options[:target] || target_project.default_branch,
- target_project: target_project
- }
+ def base_params
+ params = {}
if push_options.key?(:merge_when_pipeline_succeeds)
params.merge!(
@@ -133,17 +127,8 @@ module MergeRequests
)
end
- params
- end
-
- def update_params
- 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
- )
+ if push_options.key?(:remove_source_branch)
+ params[:force_remove_source_branch] = push_options[:remove_source_branch]
end
if push_options.key?(:target)
@@ -153,6 +138,25 @@ module MergeRequests
params
end
+ def create_params(branch)
+ params = base_params
+
+ params.merge!(
+ assignees: [current_user],
+ source_branch: branch,
+ source_project: project,
+ target_project: target_project
+ )
+
+ 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/notification_service.rb b/app/services/notification_service.rb
index 5aa804666f0..a55771ed538 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -418,7 +418,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
diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb
index bbdde4408d2..e30da0f26df 100644
--- a/app/services/projects/after_import_service.rb
+++ b/app/services/projects/after_import_service.rb
@@ -13,7 +13,7 @@ module Projects
repository.delete_all_refs_except(RESERVED_REF_PREFIXES)
end
rescue Projects::HousekeepingService::LeaseTaken => e
- Rails.logger.info(
+ Rails.logger.info( # rubocop:disable Gitlab/RailsLogger
"Could not perform housekeeping for project #{@project.full_path} (#{@project.id}): #{e}")
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 9f335cceb67..89dc4375c63 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -151,7 +151,7 @@ module Projects
log_message = message.dup
log_message << " Project ID: #{@project.id}" if @project&.id
- Rails.logger.error(log_message)
+ Rails.logger.error(log_message) # rubocop:disable Gitlab/RailsLogger
if @project && @project.persisted? && @project.import_state
@project.import_state.mark_as_failed(message)
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index d8e670e40ce..b805a7f1211 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -18,7 +18,7 @@ module Projects
schedule_stale_repos_removal
job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params)
- Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}")
+ Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}") # rubocop:disable Gitlab/RailsLogger
end
def execute
diff --git a/app/services/projects/hashed_storage/migrate_attachments_service.rb b/app/services/projects/hashed_storage/migrate_attachments_service.rb
index 3d0b8f58612..affe6e5668d 100644
--- a/app/services/projects/hashed_storage/migrate_attachments_service.rb
+++ b/app/services/projects/hashed_storage/migrate_attachments_service.rb
@@ -5,7 +5,7 @@ module Projects
class MigrateAttachmentsService < BaseAttachmentService
def initialize(project, old_disk_path, logger: nil)
@project = project
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
@old_disk_path = old_disk_path
@skipped = false
end
diff --git a/app/services/projects/hashed_storage/rollback_attachments_service.rb b/app/services/projects/hashed_storage/rollback_attachments_service.rb
index 5c6b92f965c..fb09eaa4586 100644
--- a/app/services/projects/hashed_storage/rollback_attachments_service.rb
+++ b/app/services/projects/hashed_storage/rollback_attachments_service.rb
@@ -5,7 +5,7 @@ module Projects
class RollbackAttachmentsService < BaseAttachmentService
def initialize(project, logger: nil)
@project = project
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
@old_disk_path = project.disk_path
end
diff --git a/app/services/projects/hashed_storage/rollback_service.rb b/app/services/projects/hashed_storage/rollback_service.rb
index 25767f5de5e..ee41aae64a5 100644
--- a/app/services/projects/hashed_storage/rollback_service.rb
+++ b/app/services/projects/hashed_storage/rollback_service.rb
@@ -8,7 +8,7 @@ module Projects
def initialize(project, old_disk_path, logger: nil)
@project = project
@old_disk_path = old_disk_path
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
end
def execute
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index e3491282a8a..9c6d7ef41f6 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -62,7 +62,7 @@ module Projects
end
def cleanup_and_notify_error
- Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
+ Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}") # rubocop:disable Gitlab/RailsLogger
FileUtils.rm_rf(@shared.export_path)
@@ -76,7 +76,7 @@ module Projects
end
def notify_success
- Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported")
+ Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported") # rubocop:disable Gitlab/RailsLogger
end
def notify_error
diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb
index a25c985585b..64f9b611c40 100644
--- a/app/services/projects/propagate_service_template.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -15,7 +15,7 @@ module Projects
def propagate
return unless @template.active?
- Rails.logger.info("Propagating services for template #{@template.id}")
+ Rails.logger.info("Propagating services for template #{@template.id}") # rubocop:disable Gitlab/RailsLogger
propagate_projects_with_template
end
diff --git a/app/services/projects/update_statistics_service.rb b/app/services/projects/update_statistics_service.rb
index 28677a398f3..cc6ffa9eafc 100644
--- a/app/services/projects/update_statistics_service.rb
+++ b/app/services/projects/update_statistics_service.rb
@@ -5,7 +5,7 @@ module Projects
def execute
return unless project
- Rails.logger.info("Updating statistics for project #{project.id}")
+ Rails.logger.info("Updating statistics for project #{project.id}") # rubocop:disable Gitlab/RailsLogger
project.statistics.refresh!(only: statistics.map(&:to_sym))
end
diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb
new file mode 100644
index 00000000000..e5ef8c15456
--- /dev/null
+++ b/app/services/self_monitoring/project/create_service.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+module SelfMonitoring
+ module Project
+ class CreateService < ::BaseService
+ include Stepable
+
+ DEFAULT_VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
+ DEFAULT_NAME = 'GitLab Instance Administration'
+ DEFAULT_DESCRIPTION = <<~HEREDOC
+ This project is automatically generated and will be used to help monitor this GitLab instance.
+ HEREDOC
+
+ steps :validate_admins,
+ :create_project,
+ :add_project_members,
+ :add_prometheus_manual_configuration
+
+ def initialize
+ super(nil)
+ end
+
+ def execute
+ execute_steps
+ end
+
+ private
+
+ def validate_admins
+ unless instance_admins.any?
+ log_error('No active admin user found')
+ return error('No active admin user found')
+ end
+
+ success
+ end
+
+ def create_project
+ admin_user = project_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')
+ end
+ end
+
+ def add_project_members
+ members = project.add_users(project_maintainers, Gitlab::Access::MAINTAINER)
+ errors = members.flat_map { |member| member.errors.full_messages }
+
+ if errors.any?
+ log_error("Could not add admins as members to self-monitoring project. Errors: #{errors}")
+ error('Could not add admins as members')
+ else
+ success
+ end
+ end
+
+ def add_prometheus_manual_configuration
+ 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.
+
+ service = project.find_or_initialize_service('prometheus')
+
+ unless service.update(prometheus_service_attributes)
+ log_error("Could not save prometheus manual configuration for self-monitoring project. Errors: #{service.errors.full_messages}")
+ return error('Could not save prometheus manual configuration')
+ end
+
+ success
+ end
+
+ def prometheus_enabled?
+ Gitlab.config.prometheus.enable
+ rescue Settingslogic::MissingSetting
+ false
+ end
+
+ def prometheus_listen_address
+ Gitlab.config.prometheus.listen_address
+ rescue Settingslogic::MissingSetting
+ end
+
+ def instance_admins
+ @instance_admins ||= User.admins.active
+ end
+
+ def project_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]
+ end
+
+ def create_project_params
+ {
+ initialize_with_readme: true,
+ visibility_level: DEFAULT_VISIBILITY_LEVEL,
+ name: DEFAULT_NAME,
+ description: DEFAULT_DESCRIPTION
+ }
+ end
+
+ def internal_prometheus_listen_address_uri
+ if prometheus_listen_address.starts_with?('http')
+ prometheus_listen_address
+ else
+ 'http://' + prometheus_listen_address
+ end
+ end
+
+ def prometheus_service_attributes
+ {
+ api_url: internal_prometheus_listen_address_uri,
+ manual_configuration: true,
+ active: true
+ }
+ end
+ end
+ end
+end
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 62222d3fd2a..4f10f220298 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -28,7 +28,7 @@ class SubmitUsagePingService
true
rescue Gitlab::HTTP::Error => e
- Rails.logger.info "Unable to contact GitLab, Inc.: #{e}"
+ Rails.logger.info "Unable to contact GitLab, Inc.: #{e}" # rubocop:disable Gitlab/RailsLogger
false
end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 1fee8bfcd31..6d675c026bb 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -53,7 +53,7 @@ class WebHookService
error_message: e.to_s
)
- Rails.logger.error("WebHook Error => #{e}")
+ Rails.logger.error("WebHook Error => #{e}") # rubocop:disable Gitlab/RailsLogger
{
status: :error,
diff --git a/app/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/uploaders/file_mover.rb b/app/uploaders/file_mover.rb
index 12be1e2bb22..7c7953c8a0e 100644
--- a/app/uploaders/file_mover.rb
+++ b/app/uploaders/file_mover.rb
@@ -98,7 +98,7 @@ class FileMover
end
def revert
- Rails.logger.warn("Markdown not updated, file move reverted for #{to_model}")
+ Rails.logger.warn("Markdown not updated, file move reverted for #{to_model}") # rubocop:disable Gitlab/RailsLogger
if temp_file_uploader.file_storage?
FileUtils.move(file_path, temp_file_path)
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 00b51f92b12..3b2a9d2f80e 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -23,15 +23,7 @@ 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
diff --git a/app/validators/namespace_name_validator.rb b/app/validators/namespace_name_validator.rb
deleted file mode 100644
index fb1c241037c..00000000000
--- a/app/validators/namespace_name_validator.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-# NamespaceNameValidator
-#
-# Custom validator for GitLab namespace name strings.
-class NamespaceNameValidator < ActiveModel::EachValidator
- def validate_each(record, attribute, value)
- unless value =~ Gitlab::Regex.namespace_name_regex
- record.errors.add(attribute, Gitlab::Regex.namespace_name_regex_message)
- end
- 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..986c146a9db
--- /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.blank?
+
+ record.errors.add(attribute, _('entries cannot be blank'))
+ 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/views/admin/application_settings/_pages.html.haml b/app/views/admin/application_settings/_pages.html.haml
index d7d709ffd62..1282a032f52 100644
--- a/app/views/admin/application_settings/_pages.html.haml
+++ b/app/views/admin/application_settings/_pages.html.haml
@@ -14,23 +14,22 @@
= _("Require users to prove ownership of custom domains")
.form-text.text-muted
= _("Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled")
- = link_to icon('question-circle'), help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
- - if Feature.enabled?(:pages_auto_ssl)
- %h5
- = _("Configure Let's Encrypt")
- %p
- - lets_encrypt_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: "https://letsencrypt.org/" }
- = _("%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites.").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: '</a>'.html_safe }
- .form-group
- = f.label :lets_encrypt_notification_email, _("Email"), class: 'label-bold'
- = f.text_field :lets_encrypt_notification_email, class: 'form-control'
- .form-text.text-muted
- = _("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.")
- .form-group
- .form-check
- = f.check_box :lets_encrypt_terms_of_service_accepted, class: 'form-check-input'
- = f.label :lets_encrypt_terms_of_service_accepted, class: 'form-check-label' do
- - terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path }
- = _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe }
+ = link_to icon('question-circle'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership')
+ %h5
+ = _("Configure Let's Encrypt")
+ %p
+ - lets_encrypt_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: "https://letsencrypt.org/" }
+ = _("%{lets_encrypt_link_start}Let's Encrypt%{lets_encrypt_link_end} is a free, automated, and open certificate authority (CA), that give digital certificates in order to enable HTTPS (SSL/TLS) for websites.").html_safe % { lets_encrypt_link_start: lets_encrypt_link_start, lets_encrypt_link_end: '</a>'.html_safe }
+ .form-group
+ = f.label :lets_encrypt_notification_email, _("Email"), class: 'label-bold'
+ = f.text_field :lets_encrypt_notification_email, class: 'form-control'
+ .form-text.text-muted
+ = _("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.")
+ .form-group
+ .form-check
+ = f.check_box :lets_encrypt_terms_of_service_accepted, class: 'form-check-input'
+ = f.label :lets_encrypt_terms_of_service_accepted, class: 'form-check-label' do
+ - terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path }
+ = _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe }
= f.submit _('Save changes'), class: "btn btn-success"
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 f524d35d79e..f9cc118a252 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -40,14 +40,15 @@
%strong
= @group.created_at.to_s(:medium)
+ %li
+ %span.light= _('ID:')
+ %strong
+ = @group.id
+
= render_if_exists 'admin/namespace_plan_info', namespace: @group
%li
- %span.light= _('Storage:')
- %strong= storage_counter(@group.storage_size)
- (
- = storage_counters_details(@group)
- )
+ = render 'shared/storage_counter_statistics', storage_size: @group.storage_size, storage_details: @group
%li
%span.light= _('Group Git LFS status:')
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index e23accc1ea9..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)
@@ -73,11 +78,7 @@
= @project.repository.relative_path
%li
- %span.light= _('Storage:')
- %strong= storage_counter(@project.statistics&.storage_size)
- - if @project.statistics
- = surround '(', ')' do
- = storage_counters_details(@project.statistics)
+ = render 'shared/storage_counter_statistics', storage_size: @project.statistics&.storage_size, storage_details: @project.statistics
%li
%span.light last commit:
diff --git a/app/views/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/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/status/_icon.html.haml b/app/views/ci/status/_icon.html.haml
index f38bdb2e5ed..1249b98221f 100644
--- a/app/views/ci/status/_icon.html.haml
+++ b/app/views/ci/status/_icon.html.haml
@@ -1,9 +1,10 @@
-- status = local_assigns.fetch(:status)
-- size = local_assigns.fetch(:size, 16)
-- type = local_assigns.fetch(:type, 'pipeline')
-- tooltip_placement = local_assigns.fetch(:tooltip_placement, "left")
-- path = local_assigns.fetch(:path, status.has_details? ? status.details_path : nil)
-- css_classes = "ci-status-link ci-status-icon ci-status-icon-#{status.group} has-tooltip"
+- status = local_assigns.fetch(:status)
+- size = local_assigns.fetch(:size, 16)
+- type = local_assigns.fetch(:type, 'pipeline')
+- tooltip_placement = local_assigns.fetch(:tooltip_placement, "left")
+- path = local_assigns.fetch(:path, status.has_details? ? status.details_path : nil)
+- option_css_classes = local_assigns.fetch(:option_css_classes, '')
+- css_classes = "ci-status-link ci-status-icon ci-status-icon-#{status.group} has-tooltip #{option_css_classes}"
- title = s_("PipelineStatusTooltip|Pipeline: %{ci_status}") % {ci_status: status.label}
- if type == 'commit'
- title = s_("PipelineStatusTooltip|Commit: %{ci_status}") % {ci_status: status.label}
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 37ba2143eba..b9be6028b72 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -8,7 +8,7 @@
- if current_user
.page-title-controls
= render 'shared/new_project_item_select',
- path: 'milestones/new', label: 'New milestone',
+ path: '-/milestones/new', label: 'New milestone',
include_groups: true, type: :milestones
.top-area
diff --git a/app/views/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 7dacd0b1d72..2f10f08c839 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,10 +1,10 @@
= 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 qa-login-field", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
+ = 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 qa-password-field", required: true, title: "This field is required."
+ = 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" }
@@ -17,4 +17,4 @@
= recaptcha_tags
.submit-container.move-submit-down
- = f.submit "Sign in", class: "btn btn-success qa-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 034273558bb..074edf645ba 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -7,26 +7,26 @@
= render "devise/shared/error_messages", resource: resource
.name.form-group
= f.label :name, _('Full name'), class: 'label-bold'
- = f.text_field :name, class: "form-control top qa-new-user-name 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 } }, required: true, title: _("This field is required.")
+ = 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.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
+ = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
%p.validation-error.gl-field-error-ignore.field-validation.hide= _('Username is already taken.')
%p.validation-success.gl-field-error-ignore.field-validation.hide= _('Username is available.')
%p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking username availability...')
.form-group
= f.label :email, class: 'label-bold'
- = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.")
+ = f.email_field :email, class: "form-control middle", data: { qa_selector: 'new_user_email_field' }, required: true, title: _("Please provide a valid email address.")
.form-group
= f.label :email_confirmation, class: 'label-bold'
- = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: _("Please retype the email address.")
+ = f.email_field :email_confirmation, class: "form-control middle", data: { qa_selector: 'new_user_email_confirmation_field' }, required: true, title: _("Please retype the email address.")
.form-group.append-bottom-20#password-strength
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
+ = f.password_field :password, class: "form-control bottom", data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
%p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length }
- if Gitlab::CurrentSettings.current_application_settings.enforce_terms?
.form-group
- = check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms'
+ = check_box_tag :terms_opt_in, '1', false, required: true, data: { qa_selector: 'new_user_accept_terms_checkbox' }
= label_tag :terms_opt_in do
- terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank"
- accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link }
@@ -36,4 +36,4 @@
- if show_recaptcha_sign_up?
= recaptcha_tags
.submit-container
- = f.submit _("Register"), class: "btn-register btn qa-new-user-register-button"
+ = f.submit _("Register"), class: "btn-register btn", data: { qa_selector: 'new_user_register_button' }
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 4cd03be572f..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: 'sign_in', track_event: 'click_button', track_value: 'register', 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/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index dae9a7acf6b..5d57337a568 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -46,4 +46,4 @@
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
- = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10"
+ = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10", data: { qa_selector: 'authorization_button' }
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/issues.html.haml b/app/views/groups/issues.html.haml
index 91d17cfd745..2163446425c 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,3 +1,5 @@
+- @can_bulk_update = can?(current_user, :admin_issue, @group)
+
- page_title "Issues"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
@@ -9,8 +11,15 @@
= render 'shared/issuable/nav', type: :issues
.nav-controls
= render 'shared/issuable/feed_buttons'
+
+ - if @can_bulk_update
+ = render_if_exists 'shared/issuable/bulk_update_button', 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
= render 'shared/issuable/search_bar', type: :issues
+ - if @can_bulk_update
+ = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
+
= render 'shared/issues'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 808bb1309b1..b5a2bab4799 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)
+
- 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/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 0da1f1ba7f5..d3375e00bad 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -20,6 +20,7 @@
= render_if_exists 'groups/settings/ip_restriction', 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/index.html.haml b/app/views/help/index.html.haml
index 50933c7d434..ed904c48ddb 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -33,7 +33,7 @@
.documentation-index.md
= markdown(@help_index)
.col-md-4
- .card
+ .card.links-card
.card-header
Quick help
%ul.content-list
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 496ec3c78b0..c62dce880c0 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,8 +1,8 @@
- if @group && @group.persisted? && @group.path
- - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) }
+ - group_data_attrs = { group_path: j(@group.path), name: j(@group.name), issues_path: issues_group_path(@group), mr_path: merge_requests_group_path(@group) }
- if @project && @project.persisted?
- project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? }
-.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input" } }
+.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input", track_value: "" } }
= form_tag search_path, method: :get, class: 'form-inline' do |f|
.search-input-container
.search-input-wrap
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index ff3410f6268..e9a4a068599 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html.devise-layout-html{ class: system_message_class }
= render "layouts/head"
- %body.ui-indigo.login-page.application.navless.qa-login-page{ data: { page: body_data_page } }
+ %body.ui-indigo.login-page.application.navless{ data: { page: body_data_page, qa_selector: 'login_page' } }
= header_message
.page-wrap
= render "layouts/header/empty"
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 4f3e4031fe3..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"
+ = 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 f9ee6f42e23..89f99472270 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -5,7 +5,7 @@
- else
- search_path_url = search_path
-%header.navbar.navbar-gitlab.qa-navbar.navbar-expand-sm.js-navbar
+%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
.container-fluid
.header-content
@@ -64,7 +64,7 @@
.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" } }
+ %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown", track_value: "", qa_selector: 'user_menu' } }
= 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')
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 1d7a501e5c2..e28efb09be5 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -1,4 +1,4 @@
-%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } }
+%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown", track_value: "" } }
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", id: "js-onboarding-new-project-link", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do
= sprite_icon('plus-square', size: 16)
= sprite_icon('angle-down', css_class: 'caret-down')
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 54028dc8554..cbe713b7468 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -2,7 +2,7 @@
-# https://gitlab.com/gitlab-org/gitlab-ce/issues/49713 for more information.
%ul.list-unstyled.navbar-sub-nav
- if dashboard_nav_link?(:projects)
- = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown", track_value: "" } }) do
%button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Projects')
= sprite_icon('angle-down', css_class: 'caret-down')
@@ -10,7 +10,7 @@
= render "layouts/nav/projects_dropdown/show"
- if dashboard_nav_link?(:groups)
- = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do
+ = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown", track_value: "" } }) do
%button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Groups')
= sprite_icon('angle-down', css_class: 'caret-down')
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..d1634eb62c0 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -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
@@ -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,19 +272,6 @@
= 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
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/notify/pages_domain_disabled_email.html.haml b/app/views/notify/pages_domain_disabled_email.html.haml
index 224b79bfde8..44f85df97b9 100644
--- a/app/views/notify/pages_domain_disabled_email.html.haml
+++ b/app/views/notify/pages_domain_disabled_email.html.haml
@@ -8,6 +8,6 @@
Domain: #{link_to @domain.domain, project_pages_domain_url(@project, @domain)}
%p
If this domain has been disabled in error, please follow
- = link_to 'these instructions', help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+ = link_to 'these instructions', help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership')
to verify and re-enable your domain.
= render 'removal_notification'
diff --git a/app/views/notify/pages_domain_disabled_email.text.haml b/app/views/notify/pages_domain_disabled_email.text.haml
index 4e81b054b1f..5a0fcab72d4 100644
--- a/app/views/notify/pages_domain_disabled_email.text.haml
+++ b/app/views/notify/pages_domain_disabled_email.text.haml
@@ -7,7 +7,7 @@ Domain: #{@domain.domain} (#{project_pages_domain_url(@project, @domain)})
If this domain has been disabled in error, please follow these instructions
to verify and re-enable your domain:
-= help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+= help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: 'steps')
If you no longer wish to use this domain with GitLab Pages, please remove it
from your GitLab project and delete any related DNS records.
diff --git a/app/views/notify/pages_domain_enabled_email.html.haml b/app/views/notify/pages_domain_enabled_email.html.haml
index db09e503f65..103b17a87df 100644
--- a/app/views/notify/pages_domain_enabled_email.html.haml
+++ b/app/views/notify/pages_domain_enabled_email.html.haml
@@ -7,5 +7,5 @@
Domain: #{link_to @domain.domain, project_pages_domain_url(@project, @domain)}
%p
Please visit
- = link_to 'these instructions', help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+ = link_to 'these instructions', help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: 'steps')
for more information about custom domain verification.
diff --git a/app/views/notify/pages_domain_enabled_email.text.haml b/app/views/notify/pages_domain_enabled_email.text.haml
index 1ed1dbb8315..bf8d2ac767a 100644
--- a/app/views/notify/pages_domain_enabled_email.text.haml
+++ b/app/views/notify/pages_domain_enabled_email.text.haml
@@ -5,5 +5,5 @@ Project: #{@project.human_name} (#{project_url(@project)})
Domain: #{@domain.domain} (#{project_pages_domain_url(@project, @domain)})
Please visit
-= help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+= help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: 'steps')
for more information about custom domain verification.
diff --git a/app/views/notify/pages_domain_verification_failed_email.html.haml b/app/views/notify/pages_domain_verification_failed_email.html.haml
index 03b298f8e7c..a819b66f18e 100644
--- a/app/views/notify/pages_domain_verification_failed_email.html.haml
+++ b/app/views/notify/pages_domain_verification_failed_email.html.haml
@@ -10,6 +10,6 @@
Until then, you can view your content at #{link_to @domain.url, @domain.url}
%p
Please visit
- = link_to 'these instructions', help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+ = link_to 'these instructions', help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: 'steps')
for more information about custom domain verification.
= render 'removal_notification'
diff --git a/app/views/notify/pages_domain_verification_failed_email.text.haml b/app/views/notify/pages_domain_verification_failed_email.text.haml
index c14e0e0c24d..85aa2d7a503 100644
--- a/app/views/notify/pages_domain_verification_failed_email.text.haml
+++ b/app/views/notify/pages_domain_verification_failed_email.text.haml
@@ -7,7 +7,7 @@ Unless you take action, it will be disabled on *#{@domain.enabled_until.strftime
Until then, you can view your content at #{@domain.url}
Please visit
-= help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+= help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: 'steps')
for more information about custom domain verification.
If you no longer wish to use this domain with GitLab Pages, please remove it
diff --git a/app/views/notify/pages_domain_verification_succeeded_email.html.haml b/app/views/notify/pages_domain_verification_succeeded_email.html.haml
index 2ead3187b10..808b12948f9 100644
--- a/app/views/notify/pages_domain_verification_succeeded_email.html.haml
+++ b/app/views/notify/pages_domain_verification_succeeded_email.html.haml
@@ -9,5 +9,5 @@
content at #{link_to @domain.url, @domain.url}
%p
Please visit
- = link_to 'these instructions', help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+ = link_to 'these instructions', help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: 'steps')
for more information about custom domain verification.
diff --git a/app/views/notify/pages_domain_verification_succeeded_email.text.haml b/app/views/notify/pages_domain_verification_succeeded_email.text.haml
index e7cdbdee420..8d0694ef613 100644
--- a/app/views/notify/pages_domain_verification_succeeded_email.text.haml
+++ b/app/views/notify/pages_domain_verification_succeeded_email.text.haml
@@ -6,5 +6,5 @@ Domain: #{@domain.domain} (#{project_pages_domain_url(@project, @domain)})
No action is required on your part. You can view your content at #{@domain.url}
Please visit
-= help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+= help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: 'steps')
for more information about custom domain verification.
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 6763513f9ae..95fdad125a7 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -20,6 +20,9 @@
- if vue_file_list_enabled?
#js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } }
+ - if can_edit_tree?
+ = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
+ = render 'projects/blob/new_dir'
- if @tree.readme
= render "projects/tree/readme", readme: @tree.readme
- else
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index e423631ec99..5d88be0925e 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -36,17 +36,17 @@
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", required: true
- if current_user.can_create_group?
.form-text.text-muted
- Want to house several dependent projects under the same namespace?
- = link_to "Create a group.", new_group_path
+ - link_start_group_path = '<a href="%{path}">' % { path: new_group_path }
+ - project_tip = s_('ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}') % { link_start: link_start_group_path, link_end: '</a>' }
+ = project_tip.html_safe
.form-group
= f.label :description, class: 'label-bold' do
- Project description
- %span (optional)
- = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_description", track_value: "" }
+ = s_('ProjectsNew|Project description %{tag_start}(optional)%{tag_end}').html_safe % { tag_start: '<span>'.html_safe, tag_end: '</span>'.html_safe }
+ = f.text_area :description, placeholder: s_('ProjectsNew|Description format'), class: "form-control", rows: 3, maxlength: 250, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_description", track_value: "" }
= f.label :visibility_level, class: 'label-bold' do
- Visibility Level
+ = s_('ProjectsNew|Visibility Level')
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
@@ -54,12 +54,12 @@
.form-group.row.initialize-with-readme-setting
%div{ :class => "col-sm-12" }
.form-check
- = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme" }
+ = check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme", track_value: "" }
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
- %strong Initialize repository with a README
+ %strong= s_('ProjectsNew|Initialize repository with a README')
.option-description
- Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.
+ = s_('ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.')
-= f.submit 'Create project', class: "btn btn-success project-submit", data: { track_label: "#{track_label}", track_event: "click_button", track_property: "create_project", track_value: "" }
-= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel" }
+= f.submit _('Create project'), class: "btn btn-success project-submit", data: { track_label: "#{track_label}", track_event: "click_button", track_property: "create_project", track_value: "" }
+= link_to _('Cancel'), dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel", track_value: "" }
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index c13a47b0b09..6100fd3ad37 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -3,6 +3,9 @@
- breadcrumb_title @environment.name
- page_title _("Environments")
+- content_for :page_specific_javascripts do
+ = stylesheet_link_tag 'page_bundles/xterm'
+
%div{ class: container_class }
- if can?(current_user, :stop_environment, @environment)
#stop-environment-modal.modal.fade{ tabindex: -1 }
diff --git a/app/views/projects/issues/import_csv/_modal.html.haml b/app/views/projects/issues/import_csv/_modal.html.haml
index 86bc54786ad..fe4a4236896 100644
--- a/app/views/projects/issues/import_csv/_modal.html.haml
+++ b/app/views/projects/issues/import_csv/_modal.html.haml
@@ -20,5 +20,5 @@
= _('It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected.')
= _('The maximum file size allowed is %{size}.') % { size: number_to_human_size(Gitlab::CurrentSettings.max_attachment_size.megabytes) }
.modal-footer
- %button{ type: 'submit', class: 'btn btn-success', title: _('Import issues'), data: { track_label: "export_issues_csv", track_event: "click_button"} }
+ %button{ type: 'submit', class: 'btn btn-success', title: _('Import issues'), data: { track_label: "export_issues_csv", track_event: "click_button", track_value: ""} }
= _('Import issues')
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index eb516684e52..dee3931ff04 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -38,7 +38,7 @@
= link_to_label(label, type: :merge_request, css_class: 'label-link')
.issuable-meta
- %ul.controls
+ %ul.controls.d-flex.align-items-end
- if merge_request.merged?
%li.issuable-status.d-none.d-sm-inline-block
MERGED
@@ -47,14 +47,14 @@
= icon('ban')
CLOSED
- if can?(current_user, :read_pipeline, merge_request.head_pipeline)
- %li.issuable-pipeline-status.d-none.d-sm-inline-block
- = render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user)
+ %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'
- if merge_request.open? && merge_request.broken?
- %li.issuable-pipeline-broken.d-none.d-sm-inline-block
+ %li.issuable-pipeline-broken.d-none.d-sm-flex
= link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') do
= icon('exclamation-triangle')
- if merge_request.assignees.any?
- %li
+ %li.d-flex
= render 'shared/issuable/assignees', project: merge_request.project, issuable: merge_request
= render_if_exists 'projects/merge_requests/approvals_count', merge_request: merge_request
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/new.html.haml b/app/views/projects/new.html.haml
index 1cfe302fdc7..fabe636b05c 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,7 +1,7 @@
- @hide_breadcrumbs = true
- @hide_top_links = true
-- page_title 'New Project'
-- header_title "Projects", dashboard_projects_path
+- page_title _('New Project')
+- header_title _("Projects"), dashboard_projects_path
- active_tab = local_assigns.fetch(:active_tab, 'blank')
.project-edit-container.prepend-top-default
@@ -32,17 +32,17 @@
.col-lg-9.js-toggle-container
%ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
- %a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab', track_label: 'blank_project', track_event: "click_tab" }, role: 'tab' }
- %span.d-none.d-sm-block Blank project
- %span.d-block.d-sm-none Blank
+ %a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab', track_label: 'blank_project', track_event: "click_tab", track_value: "" }, role: 'tab' }
+ %span.d-none.d-sm-block= s_('ProjectsNew|Blank project')
+ %span.d-block.d-sm-none= s_('ProjectsNew|Blank')
%li.nav-item{ role: 'presentation' }
- %a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab" }, role: 'tab' }
- %span.d-none.d-sm-block.qa-project-create-from-template-tab Create from template
- %span.d-block.d-sm-none Template
+ %a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab", track_value: "" }, role: 'tab' }
+ %span.d-none.d-sm-block.qa-project-create-from-template-tab= s_('ProjectsNew|Create from template')
+ %span.d-block.d-sm-none= s_('ProjectsNew|Template')
%li.nav-item{ role: 'presentation' }
- %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' }
- %span.d-none.d-sm-block Import project
- %span.d-block.d-sm-none Import
+ %a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab", track_value: "" }, role: 'tab' }
+ %span.d-none.d-sm-block= s_('ProjectsNew|Import project')
+ %span.d-block.d-sm-none= s_('ProjectsNew|Import')
= render_if_exists 'projects/new_ci_cd_only_project_tab', active_tab: active_tab
.tab-content.gitlab-tab-content
@@ -51,7 +51,7 @@
= render 'new_project_fields', f: f, project_name_id: "blank-project-name"
#create-from-template-pane.tab-pane.js-toggle-container.px-0.pb-0{ class: active_when(active_tab == 'template'), role: 'tabpanel' }
- .card-slim.m-4.p-4
+ .card.card-slim.m-4.p-4
%div
- contributing_templates_url = 'https://gitlab.com/gitlab-org/project-templates/contributing'
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: contributing_templates_url }
@@ -67,8 +67,8 @@
= render 'import_project_pane', active_tab: active_tab
- else
.nothing-here-block
- %h4 No import options available
- %p Contact an administrator to enable options for importing your project.
+ %h4= s_('ProjectsNew|No import options available')
+ %p= s_('ProjectsNew|Contact an administrator to enable options for importing your project.')
= render_if_exists 'projects/new_ci_cd_only_project_pane', active_tab: active_tab
@@ -76,5 +76,6 @@
.center
%h2
%i.fa.fa-spinner.fa-spin
- Creating project &amp; repository.
- %p Please wait a moment, this page will automatically refresh when ready.
+ = s_('ProjectsNew|Creating project & repository.')
+ %p
+ = s_('ProjectsNew|Please wait a moment, this page will automatically refresh when ready.')
diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml
index 5b657966909..4aa1e574d93 100644
--- a/app/views/projects/pages_domains/_form.html.haml
+++ b/app/views/projects/pages_domains/_form.html.haml
@@ -11,7 +11,7 @@
- if Gitlab.config.pages.external_https
- - auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?(@domain)
+ - auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?
- auto_ssl_enabled = @domain.auto_ssl_enabled?
- auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled
@@ -33,7 +33,7 @@
= sprite_icon("status_success_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-checked")
= sprite_icon("status_failed_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-unchecked")
%p.text-secondary.mt-3
- - docs_link_url = help_page_path("user/project/pages/lets_encrypt_for_gitlab_pages.md", anchor: "lets-encrypt-for-gitlab-pages")
+ - docs_link_url = help_page_path("user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md")
- docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url }
- docs_link_end = "</a>".html_safe
= _("Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
diff --git a/app/views/projects/pages_domains/_helper_text.html.haml b/app/views/projects/pages_domains/_helper_text.html.haml
index 5a79fefabfc..f29cb0609e6 100644
--- a/app/views/projects/pages_domains/_helper_text.html.haml
+++ b/app/views/projects/pages_domains/_helper_text.html.haml
@@ -1,9 +1,5 @@
-- docs_link_url = help_page_path("user/project/pages/getting_started_part_three.md", anchor: "adding-certificates-to-your-project")
+- docs_link_url = help_page_path("user/project/pages/custom_domains_ssl_tls_certification/index.md", anchor: "adding-an-ssltls-certificate-to-pages")
- docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url }
- docs_link_end = "</a>".html_safe
--# Hiding behind a feature flag to avoid any changes to this feature's implemention
--# when the :pages_auto_ssl feature flag is disabled. This check should be removed
--# once the :pages_auto_ssl feature flag is removed.
-- if Feature.enabled?(:pages_auto_ssl)
- %p= _("Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
+%p= _("Learn more about adding certificates to your project by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index 82147568981..e9019175219 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -53,7 +53,7 @@
.input-group-append
= clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block')
%p.form-text.text-muted
- - link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record'))
+ - link_to_help = link_to(_('verify ownership'), help_page_path('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: '4-verify-the-domains-ownership'))
= _("To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration.").html_safe % { link_to_help: link_to_help }
%tr
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_templates/_built_in_templates.html.haml b/app/views/projects/project_templates/_built_in_templates.html.haml
index 6159f1c3542..d1c09e83fd3 100644
--- a/app/views/projects/project_templates/_built_in_templates.html.haml
+++ b/app/views/projects/project_templates/_built_in_templates.html.haml
@@ -9,9 +9,9 @@
.text-muted
= template.description
.controls.d-flex.align-items-center
- %a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: template.name } }
+ %a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "template_preview", track_property: template.name, track_event: "click_button", track_value: "" } }
= _("Preview")
%label.btn.btn-success.template-button.choose-template.append-bottom-0{ for: template.name }
- %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "create_from_template", track_property: "template_use", track_event: "click_button" } }
+ %input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "template_use", track_property: template.name, track_event: "click_button", track_value: "" } }
%span
= _("Use template")
diff --git a/app/views/projects/settings/operations/_external_dashboard.html.haml b/app/views/projects/settings/operations/_external_dashboard.html.haml
index a124283921d..08d50a336fd 100644
--- a/app/views/projects/settings/operations/_external_dashboard.html.haml
+++ b/app/views/projects/settings/operations/_external_dashboard.html.haml
@@ -1,3 +1,3 @@
.js-operation-settings{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
external_dashboard: { url: metrics_external_dashboard_url,
- help_page_path: help_page_path('user/project/operations/link_to_external_dashboard') } } }
+ help_page_path: help_page_path('user/project/operations/linking_to_an_external_dashboard') } } }
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 889a13339fd..cb459b031fc 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -1,4 +1,4 @@
-.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path }
+.tree-content-holder.js-tree-content{ data: tree_content_data(@logs_path, @project, @path) }
.table-holder.bordered-box
%table.table#tree-slider{ class: "table_#{@hex_path} tree-table qa-file-tree" }
%thead
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 1d0bc588c9c..41cd044a5b0 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -11,7 +11,7 @@
- addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' }
- if vue_file_list_enabled?
- #js-repo-breadcrumb
+ #js-repo-breadcrumb{ data: breadcrumb_data_attributes }
- else
%ul.breadcrumb.repo-breadcrumb
%li.breadcrumb-item
diff --git a/app/views/projects/triggers/_content.html.haml b/app/views/projects/triggers/_content.html.haml
index 96a41aa066c..e686068657c 100644
--- a/app/views/projects/triggers/_content.html.haml
+++ b/app/views/projects/triggers/_content.html.haml
@@ -1,8 +1,9 @@
-%p.append-bottom-default
- Triggers with the
- %span.badge.badge-primary legacy
- label do not have an associated user and only have access to the current project.
- %br
- = succeed '.' do
- Learn more in the
- = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank'
+- if Feature.enabled?(:use_legacy_pipeline_triggers, @project)
+ %p.append-bottom-default
+ Triggers with the
+ %span.badge.badge-primary legacy
+ label do not have an associated user and only have access to the current project.
+ %br
+ = succeed '.' do
+ Learn more in the
+ = link_to 'triggers documentation', help_page_path('ci/triggers/README'), target: '_blank'
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 6f6f1e5e0c5..31a598ccd5e 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -8,8 +8,11 @@
.label-container
- if trigger.legacy?
- %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy
- - if !trigger.can_access_project?
+ - if trigger.supports_legacy_tokens?
+ %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy
+ - else
+ %span.badge.badge-danger.has-tooltip{ title: "Trigger is invalid due to being a legacy trigger. We recommend replacing it with a new trigger" } invalid
+ - elsif !trigger.can_access_project?
%span.badge.badge-danger.has-tooltip{ title: "Trigger user has insufficient permissions to project" } invalid
%td
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/_storage_counter_statistics.html.haml b/app/views/shared/_storage_counter_statistics.html.haml
new file mode 100644
index 00000000000..99b2323ca82
--- /dev/null
+++ b/app/views/shared/_storage_counter_statistics.html.haml
@@ -0,0 +1,4 @@
+%span.light= _('Storage:')
+%strong= storage_counter(storage_size)
+- if storage_details
+ (#{storage_counters_details(storage_details)})
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index 9fc46afe177..82ffdc9cd13 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -1,17 +1,19 @@
- Gitlab::VisibilityLevel.values.each do |level|
- disallowed = disallowed_visibility_level?(form_model, level)
- restricted = restricted_visibility_levels.include?(level)
- - disabled = disallowed || restricted
- .form-check{ class: [('disabled' if disabled), ('restricted' if restricted)] }
- = form.radio_button model_method, level, checked: (selected_level == level), disabled: disabled, class: 'form-check-input', data: { track_label: "blank_project", track_event: "activate_form_input", track_property: "#{model_method}", track_value: "#{level}" }
+ - next if disallowed || restricted
+
+ .form-check
+ = form.radio_button model_method, level, checked: (selected_level == level), class: 'form-check-input', data: { track_label: "blank_project", track_event: "activate_form_input", track_property: "#{model_method}_#{level}", track_value: "" }
= form.label "#{model_method}_#{level}", class: 'form-check-label' do
= visibility_level_icon(level)
.option-title
= visibility_level_label(level)
.option-description
= visibility_level_description(level, form_model)
- .option-disabled-reason
- - if restricted
- = restricted_visibility_level_description(level)
- - elsif disallowed
- = disallowed_visibility_level_description(level, form_model)
+
+.text-muted
+ - if all_visibility_levels_restricted?
+ = _('Visibility settings have been disabled by the administrator.')
+ - elsif multiple_visibility_levels_restricted?
+ = _('Other visibility settings have been disabled by the administrator.')
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 813fccd217b..1be230eedb9 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -14,8 +14,7 @@
%script#js-board-promotion{ type: "text/x-template" }= render_if_exists "shared/promotions/promote_issue_board"
#board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
- .d-none.d-sm-none.d-md-block
- = render 'shared/issuable/search_bar', type: :boards, board: board
+ = render 'shared/issuable/search_bar', type: :boards, board: board
.boards-list.w-100.py-3.px-2.text-nowrap
.boards-app-loading.w-100.text-center{ "v-if" => "loading" }
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/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 1bd56e064d5..07a7b5ce9de 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -17,8 +17,7 @@
= render 'shared/issuable/form/template_selector', issuable: issuable
= render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?)
-- if Gitlab::Graphql.enabled?
- #js-suggestions{ data: { project_path: @project.full_path } }
+#js-suggestions{ data: { project_path: @project.full_path } }
= render 'shared/form_elements/description', model: issuable, form: form, project: project
@@ -34,6 +33,8 @@
= render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form
+= render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form
+
= render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form
= render 'shared/issuable/form/merge_params', issuable: issuable
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 3d6c5d29d44..e253413929a 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -4,16 +4,16 @@
- 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{ class: block_css_class, "v-pre" => 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]
- if @can_bulk_update
.check-all-holder.d-none.d-sm-block.hidden
= check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
- .issues-other-filters.filtered-search-wrapper
+ .issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
.filtered-search-box
- if type != :boards_modal && type != :boards
= dropdown_tag(custom_icon('icon_history'),
@@ -147,7 +147,7 @@
%button.clear-search.hidden{ type: 'button' }
= icon('times')
- .filter-dropdown-container
+ .filter-dropdown-container.d-flex.flex-column.flex-md-row
- if type == :boards
.js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
- if user_can_admin_list
diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index 403e001bfe8..df0523595f5 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' && Feature.enabled?(:manual_sorting, default_enabled: true)
.dropdown.inline.prepend-left-10.issue-sort-dropdown
.btn-group{ role: 'group' }
diff --git a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
index 5336159e762..60dc893d9f9 100644
--- a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
+++ b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
@@ -1,4 +1,4 @@
-= form.label :assignee_id, "Assignee", class: "col-form-label #{has_due_date ? "col-lg-4" : "col-sm-2"}"
+= form.label :assignee_id, "Assignee", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
.col-sm-10{ class: ("col-md-8" if has_due_date) }
.issuable-form-select-holder.selectbox
- issuable.assignees.each do |assignee|
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 90fb067e75d..c4d1bdad2c4 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
@@ -58,7 +58,7 @@
.description.d-none.d-sm-block.append-right-default
= markdown_field(project, :description)
- .controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class }
+ .controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class }
.icon-container.d-flex.align-items-center
- if project.archived
%span.d-flex.icon-wrapper.badge.badge-warning archived
@@ -89,4 +89,4 @@
%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/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/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..bbad53c9b86
--- /dev/null
+++ b/app/workers/chaos/kill_worker.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Chaos
+ class KillWorker
+ include ApplicationWorker
+ include ChaosQueue
+
+ 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/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/concerns/new_issuable.rb b/app/workers/concerns/new_issuable.rb
index a89451a4475..22ba7c97309 100644
--- a/app/workers/concerns/new_issuable.rb
+++ b/app/workers/concerns/new_issuable.rb
@@ -27,6 +27,6 @@ module NewIssuable
# rubocop: enable CodeReuse/ActiveRecord
def log_error(record_class, record_id)
- Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job")
+ Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
index 7fac7822cf7..e3fb5d479ae 100644
--- a/app/workers/create_gpg_signature_worker.rb
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -22,7 +22,7 @@ class CreateGpgSignatureWorker
commits.each do |commit|
Gitlab::Gpg::Commit.new(commit).signature
rescue => e
- Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}")
+ Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb
index 4d0295f8d2e..efa8794b214 100644
--- a/app/workers/delete_user_worker.rb
+++ b/app/workers/delete_user_worker.rb
@@ -9,6 +9,6 @@ class DeleteUserWorker
Users::DestroyService.new(current_user).execute(delete_user, options.symbolize_keys)
rescue Gitlab::Access::AccessDeniedError => e
- Rails.logger.warn("User could not be destroyed: #{e}")
+ Rails.logger.warn("User could not be destroyed: #{e}") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index c4bcda2da16..e70bf17d5a9 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -16,7 +16,7 @@ class EmailReceiverWorker
private
def handle_failure(raw, error)
- Rails.logger.warn("Email can not be processed: #{error}\n\n#{raw}")
+ Rails.logger.warn("Email can not be processed: #{error}\n\n#{raw}") # rubocop:disable Gitlab/RailsLogger
return unless raw.present?
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index 251e95c68d5..6f0e0fd33f7 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -18,7 +18,7 @@ class ExpireBuildArtifactsWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform_legacy_artifacts_removal
- Rails.logger.info 'Scheduling removal of build artifacts'
+ Rails.logger.info 'Scheduling removal of build artifacts' # rubocop:disable Gitlab/RailsLogger
build_ids = Ci::Build.with_expired_artifacts.pluck(:id)
build_ids = build_ids.map { |build_id| [build_id] }
diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb
index 94426dcf921..71e61dcb878 100644
--- a/app/workers/expire_build_instance_artifacts_worker.rb
+++ b/app/workers/expire_build_instance_artifacts_worker.rb
@@ -12,7 +12,7 @@ class ExpireBuildInstanceArtifactsWorker
return unless build&.project && !build.project.pending_delete
- Rails.logger.info "Removing artifacts for build #{build.id}..."
+ Rails.logger.info "Removing artifacts for build #{build.id}..." # rubocop:disable Gitlab/RailsLogger
build.erase_erasable_artifacts!
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/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/new_note_worker.rb b/app/workers/new_note_worker.rb
index 98f9f45e608..1d1ea926c21 100644
--- a/app/workers/new_note_worker.rb
+++ b/app/workers/new_note_worker.rb
@@ -11,7 +11,7 @@ class NewNoteWorker
NotificationService.new.new_note(note) unless skip_notification?(note)
Notes::PostProcessService.new(note).execute
else
- Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
+ Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb
index 12400d4e025..55ac7cd9b3c 100644
--- a/app/workers/object_storage/migrate_uploads_worker.rb
+++ b/app/workers/object_storage/migrate_uploads_worker.rb
@@ -37,6 +37,7 @@ module ObjectStorage
end
end
+ # rubocop:disable Gitlab/RailsLogger
def report!(results)
success, failures = results.partition(&:success?)
@@ -45,6 +46,7 @@ module ObjectStorage
raise MigrationFailures.new(failures.map(&:error)) if failures.any?
end
+ # rubocop:enable Gitlab/RailsLogger
def header(success, failures)
_("Migrated %{success_count}/%{total_count} files.") % { success_count: success.count, total_count: success.count + failures.count }
@@ -98,7 +100,7 @@ module ObjectStorage
report!(results)
rescue SanityCheckError => e
# do not retry: the job is insane
- Rails.logger.warn "#{self.class}: Sanity check error (#{e.message})"
+ Rails.logger.warn "#{self.class}: Sanity check error (#{e.message})" # rubocop:disable Gitlab/RailsLogger
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
index 40c34d29970..e5dde07a648 100644
--- a/app/workers/pages_domain_ssl_renewal_cron_worker.rb
+++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
@@ -5,9 +5,9 @@ class PagesDomainSslRenewalCronWorker
include CronjobQueue
def perform
- PagesDomain.need_auto_ssl_renewal.find_each do |domain|
- next unless ::Gitlab::LetsEncrypt.enabled?(domain)
+ return unless ::Gitlab::LetsEncrypt.enabled?
+ PagesDomain.need_auto_ssl_renewal.find_each do |domain|
PagesDomainSslRenewalWorker.perform_async(domain.id)
end
end
diff --git a/app/workers/pages_domain_ssl_renewal_worker.rb b/app/workers/pages_domain_ssl_renewal_worker.rb
index b32458ca777..87fd8059946 100644
--- a/app/workers/pages_domain_ssl_renewal_worker.rb
+++ b/app/workers/pages_domain_ssl_renewal_worker.rb
@@ -6,7 +6,7 @@ class PagesDomainSslRenewalWorker
def perform(domain_id)
domain = PagesDomain.find_by_id(domain_id)
return unless domain&.enabled?
- return unless ::Gitlab::LetsEncrypt.enabled?(domain)
+ return unless ::Gitlab::LetsEncrypt.enabled?
::PagesDomains::ObtainLetsEncryptCertificateService.new(domain).execute
end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index a9b88a133be..35e9c58eb13 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -35,7 +35,7 @@ class RepositoryForkWorker
def start_fork(project)
return true if start(project.import_state)
- Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.")
+ Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.") # rubocop:disable Gitlab/RailsLogger
false
end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 59691f48a39..dff9c8f50bf 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -36,7 +36,7 @@ class RepositoryImportWorker
def start_import
return true if start(project.import_state)
- Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.")
+ Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.") # rubocop:disable Gitlab/RailsLogger
false
end
diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb
index c0bae08ba85..03a7ff2cd7a 100644
--- a/app/workers/repository_update_remote_mirror_worker.rb
+++ b/app/workers/repository_update_remote_mirror_worker.rb
@@ -45,6 +45,6 @@ class RepositoryUpdateRemoteMirrorWorker
def fail_remote_mirror(remote_mirror, message)
remote_mirror.mark_as_failed(message)
- Rails.logger.error(message)
+ Rails.logger.error(message) # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb
index 43e0b9db22f..351850e53cb 100644
--- a/app/workers/run_pipeline_schedule_worker.rb
+++ b/app/workers/run_pipeline_schedule_worker.rb
@@ -30,6 +30,7 @@ class RunPipelineScheduleWorker
private
+ # rubocop:disable Gitlab/RailsLogger
def error(schedule, error)
failed_creation_counter.increment
@@ -41,6 +42,7 @@ class RunPipelineScheduleWorker
issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
extra: { schedule_id: schedule.id })
end
+ # rubocop:enable Gitlab/RailsLogger
def failed_creation_counter
@failed_creation_counter ||=
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index 25809f68080..30fba038937 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -14,7 +14,7 @@ class StuckCiJobsWorker
def perform
return unless try_obtain_lease
- Rails.logger.info "#{self.class}: Cleaning stuck builds"
+ Rails.logger.info "#{self.class}: Cleaning stuck builds" # rubocop:disable Gitlab/RailsLogger
drop :running, BUILD_RUNNING_OUTDATED_TIMEOUT, 'ci_builds.updated_at < ?', :stuck_or_timeout_failure
drop :pending, BUILD_PENDING_OUTDATED_TIMEOUT, 'ci_builds.updated_at < ?', :stuck_or_timeout_failure
@@ -66,7 +66,7 @@ class StuckCiJobsWorker
# rubocop: enable CodeReuse/ActiveRecord
def drop_build(type, build, status, timeout, reason)
- Rails.logger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{status}, timeout: #{timeout}, reason: #{reason})"
+ Rails.logger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{status}, timeout: #{timeout}, reason: #{reason})" # rubocop:disable Gitlab/RailsLogger
Gitlab::OptimisticLocking.retry_lock(build, 3) do |b|
b.drop(reason)
end
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index c8a186ba4ce..a9ff5b22b25 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -38,7 +38,7 @@ class StuckImportJobsWorker
completed_import_states = enqueued_import_states_with_jid.where(id: completed_import_state_ids)
completed_import_state_jids = completed_import_states.map { |import_state| import_state.jid }.join(', ')
- Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_import_state_jids}")
+ Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_import_state_jids}") # rubocop:disable Gitlab/RailsLogger
completed_import_states.each do |import_state|
import_state.mark_as_failed(error_message)
diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb
index f34ed6c4844..e840ae47421 100644
--- a/app/workers/stuck_merge_jobs_worker.rb
+++ b/app/workers/stuck_merge_jobs_worker.rb
@@ -5,7 +5,7 @@ class StuckMergeJobsWorker
include CronjobQueue
def self.logger
- Rails.logger
+ Rails.logger # rubocop:disable Gitlab/RailsLogger
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb
index 3297a1fe3d0..55b599ba38f 100644
--- a/app/workers/trending_projects_worker.rb
+++ b/app/workers/trending_projects_worker.rb
@@ -5,7 +5,7 @@ class TrendingProjectsWorker
include CronjobQueue
def perform
- Rails.logger.info('Refreshing trending projects')
+ Rails.logger.info('Refreshing trending projects') # rubocop:disable Gitlab/RailsLogger
TrendingProject.refresh!
end
diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb
index c7213df652a..6c0e472e05a 100644
--- a/app/workers/update_merge_requests_worker.rb
+++ b/app/workers/update_merge_requests_worker.rb
@@ -27,7 +27,7 @@ class UpdateMergeRequestsWorker
"ref=#{ref}"
].join(',')
- Rails.logger.info("UpdateMergeRequestsWorker#perform #{args_log}") if time.real > LOG_TIME_THRESHOLD
+ Rails.logger.info("UpdateMergeRequestsWorker#perform #{args_log}") if time.real > LOG_TIME_THRESHOLD # rubocop:disable Gitlab/RailsLogger
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/upload_checksum_worker.rb b/app/workers/upload_checksum_worker.rb
index 2a0536106d7..834dcaa435d 100644
--- a/app/workers/upload_checksum_worker.rb
+++ b/app/workers/upload_checksum_worker.rb
@@ -8,6 +8,6 @@ class UploadChecksumWorker
upload.calculate_checksum!
upload.save!
rescue ActiveRecord::RecordNotFound
- Rails.logger.error("UploadChecksumWorker: couldn't find upload #{upload_id}, skipping")
+ Rails.logger.error("UploadChecksumWorker: couldn't find upload #{upload_id}, skipping") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/bin/bundle b/bin/bundle
index 66e9889e8b4..f19acf5b5cc 100755
--- a/bin/bundle
+++ b/bin/bundle
@@ -1,3 +1,3 @@
#!/usr/bin/env ruby
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
load Gem.bin_path('bundler', 'bundle')
diff --git a/bin/setup b/bin/setup
index 883825bc0a2..94fd4d79775 100755
--- a/bin/setup
+++ b/bin/setup
@@ -1,33 +1,36 @@
#!/usr/bin/env ruby
-
-require "pathname"
+require 'fileutils'
+include FileUtils
# path to your application root.
-APP_ROOT = Pathname.new File.expand_path("../../", __FILE__)
+APP_ROOT = File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
-Dir.chdir APP_ROOT do
- # This script is a starting point to set up your application.
- # Add necessary setup steps to this file:
+chdir APP_ROOT do
+ # This script is a starting point to setup your application.
+ # Add necessary setup steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
- puts "== Installing dependencies =="
- system! "gem install bundler --conservative"
- system("bundle check") || system!("bundle install")
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
# puts "\n== Copying sample files =="
- # unless File.exist?("config/database.yml")
- # cp "config/database.yml.sample", "config/database.yml"
+ # unless File.exist?('config/database.yml')
+ # cp 'config/database.yml.sample', 'config/database.yml'
# end
puts "\n== Preparing database =="
- system! "bin/rails db:setup"
+ system! 'bin/rails db:setup'
puts "\n== Removing old logs and tempfiles =="
- system! "bin/rails log:clear tmp:clear"
+ system! 'bin/rails log:clear tmp:clear'
puts "\n== Restarting application server =="
- system! "bin/rails restart"
+ system! 'bin/rails restart'
end
diff --git a/bin/update b/bin/update
index a8e4462f203..58bfaed518c 100755
--- a/bin/update
+++ b/bin/update
@@ -1,10 +1,9 @@
#!/usr/bin/env ruby
-require 'pathname'
require 'fileutils'
include FileUtils
# path to your application root.
-APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+APP_ROOT = File.expand_path('..', __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
@@ -18,6 +17,9 @@ chdir APP_ROOT do
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
+ # Install JavaScript dependencies if using Yarn
+ # system('bin/yarn')
+
puts "\n== Updating database =="
system! 'bin/rails db:migrate'
diff --git a/bin/web b/bin/web
index 06ff7c39296..f640abf0fbc 100755
--- a/bin/web
+++ b/bin/web
@@ -1,63 +1,21 @@
#!/bin/sh
+set -e
+
cd $(dirname $0)/..
app_root=$(pwd)
-# Switch to experimental PUMA configuration
-if [ -n "${EXPERIMENTAL_PUMA}" ]; then
- exec bin/web_puma "$@"
-fi
-
-unicorn_pidfile="$app_root/tmp/pids/unicorn.pid"
-unicorn_config="$app_root/config/unicorn.rb"
-unicorn_cmd="bundle exec unicorn_rails -c $unicorn_config -E $RAILS_ENV"
-
-get_unicorn_pid()
-{
- local pid=$(cat $unicorn_pidfile)
- if [ -z "$pid" ] ; then
- echo "Could not find a PID in $unicorn_pidfile"
- exit 1
- fi
- unicorn_pid=$pid
-}
-
-start()
-{
- exec $unicorn_cmd -D
-}
-
-start_foreground()
-{
- exec $unicorn_cmd
-}
-
-stop()
-{
- get_unicorn_pid
- kill -QUIT $unicorn_pid
-}
+case "$USE_WEB_SERVER" in
+ puma|"") # and the "" defines default
+ exec bin/web_puma "$@"
+ ;;
-reload()
-{
- get_unicorn_pid
- kill -USR2 $unicorn_pid
-}
+ unicorn)
+ exec bin/web_unicorn "$@"
+ ;;
-case "$1" in
- start)
- start
- ;;
- start_foreground)
- start_foreground
- ;;
- stop)
- stop
- ;;
- reload)
- reload
- ;;
- *)
- echo "Usage: RAILS_ENV=your_env $0 {start|stop|reload}"
- ;;
+ *)
+ echo "Unkown web server used by USE_WEB_SERVER: $USE_WEB_SERVER."
+ exit 1
+ ;;
esac
diff --git a/bin/web_unicorn b/bin/web_unicorn
new file mode 100755
index 00000000000..ecd0bbd10b0
--- /dev/null
+++ b/bin/web_unicorn
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+cd $(dirname $0)/..
+app_root=$(pwd)
+
+unicorn_pidfile="$app_root/tmp/pids/unicorn.pid"
+unicorn_config="$app_root/config/unicorn.rb"
+unicorn_cmd="bundle exec unicorn_rails -c $unicorn_config -E $RAILS_ENV"
+
+get_unicorn_pid()
+{
+ local pid=$(cat $unicorn_pidfile)
+ if [ -z "$pid" ] ; then
+ echo "Could not find a PID in $unicorn_pidfile"
+ exit 1
+ fi
+ unicorn_pid=$pid
+}
+
+start()
+{
+ exec $unicorn_cmd -D
+}
+
+start_foreground()
+{
+ exec $unicorn_cmd
+}
+
+stop()
+{
+ get_unicorn_pid
+ kill -QUIT $unicorn_pid
+}
+
+reload()
+{
+ get_unicorn_pid
+ kill -USR2 $unicorn_pid
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ start_foreground)
+ start_foreground
+ ;;
+ stop)
+ stop
+ ;;
+ reload)
+ reload
+ ;;
+ *)
+ echo "Usage: RAILS_ENV=your_env $0 {start|stop|reload}"
+ ;;
+esac
diff --git a/bin/yarn b/bin/yarn
new file mode 100755
index 00000000000..460dd565b4a
--- /dev/null
+++ b/bin/yarn
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+APP_ROOT = File.expand_path('..', __dir__)
+Dir.chdir(APP_ROOT) do
+ begin
+ exec "yarnpkg", *ARGV
+ rescue Errno::ENOENT
+ $stderr.puts "Yarn executable was not detected in the system."
+ $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
+ exit 1
+ end
+end
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/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/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/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/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/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
index 1e6642ec102..da50ee32c83 100644
--- a/changelogs/unreleased/-30974-issue-search-by-number.yml
+++ b/changelogs/unreleased/30974-issue-search-by-number.yml
@@ -1,5 +1,5 @@
---
title: "Search issuables by iids"
-merge_request: !28302
+merge_request: 28302
author: Riccardo Padovani
type: fixed
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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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-sprockets-paths.yml b/changelogs/unreleased/64091-fix-sprockets-paths.yml
new file mode 100644
index 00000000000..fcd8b2faa49
--- /dev/null
+++ b/changelogs/unreleased/64091-fix-sprockets-paths.yml
@@ -0,0 +1,5 @@
+---
+title: Fix xterm css not loading for environment terminal
+merge_request: 31023
+author:
+type: fixed
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/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/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/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/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/64731-fix-project-auto-devops-api.yml b/changelogs/unreleased/64731-fix-project-auto-devops-api.yml
new file mode 100644
index 00000000000..7fe2c036773
--- /dev/null
+++ b/changelogs/unreleased/64731-fix-project-auto-devops-api.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the project auto devops API
+merge_request: 30946
+author:
+type: fixed
diff --git a/changelogs/unreleased/64870-can-t-save-pages-domain-form-with-let-s-encrypt-enabled-if-current-certificate-is-outdated.yml b/changelogs/unreleased/64870-can-t-save-pages-domain-form-with-let-s-encrypt-enabled-if-current-certificate-is-outdated.yml
new file mode 100644
index 00000000000..291901d64ed
--- /dev/null
+++ b/changelogs/unreleased/64870-can-t-save-pages-domain-form-with-let-s-encrypt-enabled-if-current-certificate-is-outdated.yml
@@ -0,0 +1,6 @@
+---
+title: Fix "Certificate misses intermediates" UI error when enabling Let's Encrypt
+ integration for pages domain
+merge_request: 30995
+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/65019-auto-devops-dind-tls-fix.yml b/changelogs/unreleased/65019-auto-devops-dind-tls-fix.yml
new file mode 100644
index 00000000000..3eea3e551ce
--- /dev/null
+++ b/changelogs/unreleased/65019-auto-devops-dind-tls-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Set DOCKER_TLS_CERTDIR in Auto Dev-Ops CI template to fix jobs using Docker-in-Docker
+merge_request: 31078
+author:
+type: fixed
diff --git a/changelogs/unreleased/65019-job-templates-dind-tls-fix.yml b/changelogs/unreleased/65019-job-templates-dind-tls-fix.yml
new file mode 100644
index 00000000000..c7c02486d61
--- /dev/null
+++ b/changelogs/unreleased/65019-job-templates-dind-tls-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Set DOCKER_TLS_CERTDIR in CI job templates to fix Docker-in-Docker service
+merge_request: 31080
+author:
+type: fixed
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/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/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-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-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/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-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/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/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-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/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/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/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-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/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/dm-submodule-helper-routing.yml b/changelogs/unreleased/dm-submodule-helper-routing.yml
new file mode 100644
index 00000000000..779d4d167df
--- /dev/null
+++ b/changelogs/unreleased/dm-submodule-helper-routing.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug that caused diffs not to show on MRs with changes to submodules
+merge_request:
+author:
+type: fixed
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/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/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/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-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/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/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-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-i18n-updated-projects.yml b/changelogs/unreleased/fix-i18n-updated-projects.yml
new file mode 100644
index 00000000000..408ee438480
--- /dev/null
+++ b/changelogs/unreleased/fix-i18n-updated-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Properly translate term in projects list
+merge_request: 30958
+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-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/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/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.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/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/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-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/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/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/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-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/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/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/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/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-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/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/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/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/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/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/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_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/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/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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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/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/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/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-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-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-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/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-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/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/application.rb b/config/application.rb
index c5ef6a2c60d..92240426b5a 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -1,11 +1,18 @@
-require File.expand_path('boot', __dir__)
+require_relative 'boot'
-require 'rails/all'
+# Based on https://github.com/rails/rails/blob/v5.2.3/railties/lib/rails/all.rb
+# Only load the railties we need instead of loading everything
+require 'active_record/railtie'
+require 'action_controller/railtie'
+require 'action_view/railtie'
+require 'action_mailer/railtie'
+require 'rails/test_unit/railtie'
-Bundler.require(:default, Rails.env)
+Bundler.require(*Rails.groups)
module Gitlab
class Application < Rails::Application
+ require_dependency Rails.root.join('lib/gitlab')
require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
require_dependency Rails.root.join('lib/gitlab/redis/cache')
require_dependency Rails.root.join('lib/gitlab/redis/queues')
@@ -15,15 +22,12 @@ 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.
+ config.active_record.sqlite3.represent_boolean_as_integer = true
+
# Sidekiq uses eager loading, but directories not in the standard Rails
# directories must be added to the eager load paths:
# https://github.com/mperham/sidekiq/wiki/FAQ#why-doesnt-sidekiq-autoload-my-rails-application-code
@@ -85,13 +89,6 @@ module Gitlab
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
- # ActionCable mount point.
- # The default Rails' mount point is `/cable` which may conflict with existing
- # namespaces/users.
- # https://github.com/rails/rails/blob/5-0-stable/actioncable/lib/action_cable.rb#L38
- # Please change this value when configuring ActionCable for real usage.
- config.action_cable.mount_path = "/-/cable"
-
# Configure sensitive parameters which will be filtered from the log file.
#
# Parameters filtered:
@@ -166,7 +163,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"
@@ -181,9 +177,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|
@@ -271,5 +277,10 @@ 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/brakeman.ignore b/config/brakeman.ignore
new file mode 100644
index 00000000000..0e4fef65781
--- /dev/null
+++ b/config/brakeman.ignore
@@ -0,0 +1,24 @@
+{
+ "ignored_warnings": [
+ {
+ "warning_type": "Cross-Site Request Forgery",
+ "warning_code": 7,
+ "fingerprint": "dc562678129557cdb8b187217da304044547a3605f05fe678093dcb4b4d8bbe4",
+ "message": "'protect_from_forgery' should be called in Oauth::GeoAuthController",
+ "file": "app/controllers/oauth/geo_auth_controller.rb",
+ "line": 1,
+ "link": "http://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
+ "code": null,
+ "render_path": null,
+ "location": {
+ "type": "controller",
+ "controller": "Oauth::GeoAuthController"
+ },
+ "user_input": null,
+ "confidence": "High",
+ "note": ""
+ }
+ ],
+ "updated": "2017-01-20 02:06:54 +0000",
+ "brakeman_version": "3.4.1"
+}
diff --git a/config/database.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/database_geo.yml.postgresql b/config/database_geo.yml.postgresql
new file mode 100644
index 00000000000..2918879f7ed
--- /dev/null
+++ b/config/database_geo.yml.postgresql
@@ -0,0 +1,51 @@
+#
+# PRODUCTION
+#
+production:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_production
+ pool: 10
+ username: git
+ password: "secure password"
+ host: localhost
+ fdw: true
+
+#
+# Development specific
+#
+development:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_development
+ pool: 5
+ username: postgres
+ password: "secure password"
+ host: localhost
+ fdw: true
+
+#
+# Staging specific
+#
+staging:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_staging
+ pool: 10
+ username: git
+ password: "secure password"
+ host: localhost
+ fdw: true
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test: &test
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_test
+ pool: 5
+ username: postgres
+ password:
+ host: localhost
+ fdw: true
diff --git a/config/environments/development.rb b/config/environments/development.rb
index ac9b02b08d5..3881f1be152 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -7,6 +7,7 @@ Rails.application.configure do
config.cache_classes = false
# Show full error reports and disable caching
+ config.active_record.verbose_query_logs = true
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
diff --git a/config/environments/test.rb b/config/environments/test.rb
index e7166882eea..153d16e4e55 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -23,6 +23,7 @@ Rails.application.configure do
config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' }
# Show full error reports and disable caching
+ config.active_record.verbose_query_logs = true
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
@@ -40,7 +41,7 @@ Rails.application.configure do
# Print deprecation notices to the stderr
config.active_support.deprecation = :stderr
- config.eager_load = false
+ config.eager_load = true
config.cache_store = :null_store
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index c82d9b5ceef..0e78980350f 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -664,6 +664,9 @@ production: &base
# Port where the client side certificate is requested by the webserver (NGINX/Apache)
# client_certificate_required_port: 3444
+ # Browser session with smartcard sign-in is required for Git access
+ # required_for_git_access: false
+
## Kerberos settings
kerberos:
# Allow the HTTP Negotiate authentication method for Git clients
@@ -949,6 +952,16 @@ production: &base
# address: localhost
# port: 3807
+ ## Prometheus settings
+ # Do not modify these settings here. They should be modified in /etc/gitlab/gitlab.rb
+ # if you installed GitLab via Omnibus.
+ # If you installed from source, you need to install and configure Prometheus
+ # yourself, and then update the values here.
+ # https://docs.gitlab.com/ee/administration/monitoring/prometheus/
+ prometheus:
+ # enable: true
+ # listen_address: 'localhost:9090'
+
#
# 5. Extra customization
# ==========================
@@ -1155,6 +1168,9 @@ test:
user_filter: ''
group_base: 'ou=groups,dc=example,dc=com'
admin_group: ''
+ prometheus:
+ enable: true
+ listen_address: 'localhost:9090'
staging:
<<: *base
diff --git a/config/initializers/0_license.rb b/config/initializers/0_license.rb
new file mode 100644
index 00000000000..f750022dfdf
--- /dev/null
+++ b/config/initializers/0_license.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+Gitlab.ee do
+ begin
+ public_key_file = File.read(Rails.root.join(".license_encryption_key.pub"))
+ public_key = OpenSSL::PKey::RSA.new(public_key_file)
+ Gitlab::License.encryption_key = public_key
+ rescue
+ warn "WARNING: No valid license encryption key provided."
+ end
+
+ # Needed to run migration
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses')
+ message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false)
+ if ::License.block_changes? && message.present?
+ warn "WARNING: #{message}"
+ end
+ end
+end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index bf187e9a282..494c4dd1f93 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -55,7 +55,7 @@ if Settings.ldap['enabled'] || Rails.env.test?
server['tls_options'] ||= {}
if server['ssl_version'] || server['ca_file']
- Rails.logger.warn 'DEPRECATED: LDAP options `ssl_version` and `ca_file` should be nested within `tls_options`'
+ Rails.logger.warn 'DEPRECATED: LDAP options `ssl_version` and `ca_file` should be nested within `tls_options`' # rubocop:disable Gitlab/RailsLogger
end
if server['ssl_version']
@@ -76,6 +76,7 @@ Gitlab.ee do
Settings['smartcard'] ||= Settingslogic.new({})
Settings.smartcard['enabled'] = false if Settings.smartcard['enabled'].nil?
Settings.smartcard['client_certificate_required_port'] = 3444 if Settings.smartcard['client_certificate_required_port'].nil?
+ Settings.smartcard['required_for_git_access'] = false if Settings.smartcard['required_for_git_access'].nil?
end
Settings['omniauth'] ||= Settingslogic.new({})
@@ -216,6 +217,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
#
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 741c8ef1ca0..3f2691dde95 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
@@ -17,13 +16,13 @@ def prometheus_default_multiproc_dir
end
Prometheus::Client.configure do |config|
- config.logger = Rails.logger
+ config.logger = Rails.logger # rubocop:disable Gitlab/RailsLogger
config.initial_mmap_file_size = 4 * 1024
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|
diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb
index e95157bfde5..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)
- 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)
- 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_lifecycle.rb b/config/initializers/active_record_lifecycle.rb
index 7fa37121efc..61f1d299960 100644
--- a/config/initializers/active_record_lifecycle.rb
+++ b/config/initializers/active_record_lifecycle.rb
@@ -7,7 +7,7 @@ if defined?(ActiveRecord::Base) && !Sidekiq.server?
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.establish_connection
- Rails.logger.debug("ActiveRecord connection established")
+ Rails.logger.debug("ActiveRecord connection established") # rubocop:disable Gitlab/RailsLogger
end
end
end
@@ -18,6 +18,6 @@ if defined?(ActiveRecord::Base)
# as there's no need for the master process to hold a connection
ActiveRecord::Base.connection.disconnect!
- Rails.logger.debug("ActiveRecord connection disconnected")
+ Rails.logger.debug("ActiveRecord connection disconnected") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/config/initializers/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/active_record_preloader.rb b/config/initializers/active_record_preloader.rb
index 3b16014f302..a293909149e 100644
--- a/config/initializers/active_record_preloader.rb
+++ b/config/initializers/active_record_preloader.rb
@@ -1,9 +1,22 @@
module ActiveRecord
module Associations
class Preloader
+ class NullPreloader
+ def self.new(klass, owners, reflection, preload_scope)
+ self
+ end
+
+ def self.run(preloader)
+ end
+
+ def self.preloaded_records
+ []
+ end
+ end
+
module NoCommitPreloader
- def preloader_for(reflection, owners, rhs_klass)
- return NullPreloader if rhs_klass == ::Commit
+ def preloader_for(reflection, owners)
+ return NullPreloader if owners.first.association(reflection.name).klass == ::Commit
super
end
diff --git a/config/initializers/active_record_query_cache.rb b/config/initializers/active_record_query_cache.rb
new file mode 100644
index 00000000000..61505a1edd3
--- /dev/null
+++ b/config/initializers/active_record_query_cache.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+ActiveRecord::ConnectionAdapters::ConnectionPool.prepend Gitlab::Patch::ActiveRecordQueryCache
diff --git a/config/initializers/active_record_verbose_query_logs.rb b/config/initializers/active_record_verbose_query_logs.rb
deleted file mode 100644
index 1c5fbc8e830..00000000000
--- a/config/initializers/active_record_verbose_query_logs.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-# frozen_string_literal: true
-
-# This is backport of https://github.com/rails/rails/pull/26815/files
-# Enabled by default for every non-production environment
-
-module ActiveRecord
- class LogSubscriber
- module VerboseQueryLogs
- def debug(progname = nil, &block)
- return unless super
-
- log_query_source
- end
-
- def log_query_source
- source_line, line_number = extract_callstack(caller_locations)
-
- if source_line
- if defined?(::Rails.root)
- app_root = "#{::Rails.root}/".freeze
- source_line = source_line.sub(app_root, "")
- end
-
- logger.debug(" ↳ #{source_line}:#{line_number}")
- end
- end
-
- def extract_callstack(callstack)
- line = callstack.find do |frame|
- frame.absolute_path && !ignored_callstack(frame.absolute_path)
- end
-
- offending_line = line || callstack.first
- [
- offending_line.path,
- offending_line.lineno,
- offending_line.label
- ]
- end
-
- LOG_SUBSCRIBER_FILE = ActiveRecord::LogSubscriber.method(:logger).source_location.first
- RAILS_GEM_ROOT = File.expand_path("../../../..", LOG_SUBSCRIBER_FILE) + "/"
- APP_CONFIG_ROOT = File.expand_path("..", __dir__) + "/"
-
- def ignored_callstack(path)
- path.start_with?(APP_CONFIG_ROOT, RAILS_GEM_ROOT, RbConfig::CONFIG["rubylibdir"])
- end
- end
-
- if Rails.version.start_with?("5.2")
- raise "Remove this monkey patch: #{__FILE__}"
- else
- prepend(VerboseQueryLogs) unless Rails.env.production?
- end
- end
-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/ar_speed_up_migration_checking.rb b/config/initializers/ar_speed_up_migration_checking.rb
index aae774daa35..c4ffcc54cb2 100644
--- a/config/initializers/ar_speed_up_migration_checking.rb
+++ b/config/initializers/ar_speed_up_migration_checking.rb
@@ -2,16 +2,14 @@ if Rails.env.test?
require 'active_record/migration'
module ActiveRecord
- class Migrator
- class << self
- alias_method :migrations_unmemoized, :migrations
+ class MigrationContext
+ alias_method :migrations_unmemoized, :migrations
- # This method is called a large number of times per rspec example, and
- # it reads + parses `db/migrate/*` each time. Memoizing it can save 0.5
- # seconds per spec.
- def migrations(paths)
- (@migrations ||= migrations_unmemoized(paths)).dup
- end
+ # This method is called a large number of times per rspec example, and
+ # it reads + parses `db/migrate/*` each time. Memoizing it can save 0.5
+ # seconds per spec.
+ def migrations
+ @migrations ||= migrations_unmemoized
end
end
end
diff --git a/config/initializers/config_initializers_active_record_locking.rb b/config/initializers/config_initializers_active_record_locking.rb
index 608d63223a3..915247826e9 100644
--- a/config/initializers/config_initializers_active_record_locking.rb
+++ b/config/initializers/config_initializers_active_record_locking.rb
@@ -22,10 +22,11 @@ module ActiveRecord
# Patched because when `lock_version` is read as `0`, it may actually be `NULL` in the DB.
possible_previous_lock_value = previous_lock_value.to_i == 0 ? [nil, 0] : previous_lock_value
- affected_rows = self.class.unscoped._update_record(
- arel_attributes_with_values(attribute_names),
- self.class.primary_key => id_in_database,
- locking_column => possible_previous_lock_value
+ affected_rows = self.class.unscoped.where(
+ locking_column => possible_previous_lock_value,
+ self.class.primary_key => id_in_database
+ ).update_all(
+ attributes_with_values_for_update(attribute_names)
)
if affected_rows != 1
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/console_message.rb b/config/initializers/console_message.rb
index 05eb395028d..04c109aa844 100644
--- a/config/initializers/console_message.rb
+++ b/config/initializers/console_message.rb
@@ -2,9 +2,18 @@
if defined?(Rails::Console)
# note that this will not print out when using `spring`
justify = 15
- puts "-------------------------------------------------------------------------------------"
+
+ puts '-' * 80
puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision})"
puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)}"
puts " #{Gitlab::Database.human_adapter_name}:".ljust(justify) + Gitlab::Database.version
- puts "-------------------------------------------------------------------------------------"
+
+ Gitlab.ee do
+ if Gitlab::Geo.enabled?
+ puts " Geo enabled:".ljust(justify) + 'yes'
+ puts " Geo server:".ljust(justify) + EE::GeoHelper.current_node_human_status
+ end
+ end
+
+ puts '-' * 80
end
diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb
index 14616e726d9..0d096e34eb7 100644
--- a/config/initializers/deprecations.rb
+++ b/config/initializers/deprecations.rb
@@ -2,7 +2,7 @@ if Rails.env.development? || ENV['GITLAB_LEGACY_PATH_LOG_MESSAGE']
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
deprecator.behavior = -> (message, callstack) {
- Rails.logger.warn("#{message}: #{callstack[1..20].join}")
+ Rails.logger.warn("#{message}: #{callstack[1..20].join}") # rubocop:disable Gitlab/RailsLogger
}
ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator)
diff --git a/config/initializers/elastic_client_setup.rb b/config/initializers/elastic_client_setup.rb
new file mode 100644
index 00000000000..2ecb7956007
--- /dev/null
+++ b/config/initializers/elastic_client_setup.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+
+require 'gitlab/current_settings'
+
+Gitlab.ee do
+ Elasticsearch::Model::Response::Records.prepend GemExtensions::Elasticsearch::Model::Response::Records
+ Elasticsearch::Model::Adapter::Multiple::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::Multiple::Records
+ Elasticsearch::Model::Indexing::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Indexing::InstanceMethods
+
+ module Elasticsearch
+ module Model
+ module Client
+ # This mutex is only used to synchronize *creation* of a new client, so
+ # all including classes can share the same client instance
+ CLIENT_MUTEX = Mutex.new
+
+ cattr_accessor :cached_client
+ cattr_accessor :cached_config
+
+ module ClassMethods
+ # Override the default ::Elasticsearch::Model::Client implementation to
+ # return a client configured from application settings. All including
+ # classes will use the same instance, which is refreshed automatically
+ # if the settings change.
+ #
+ # _client is present to match the arity of the overridden method, where
+ # it is also not used.
+ #
+ # @return [Elasticsearch::Transport::Client]
+ def client(_client = nil)
+ store = ::Elasticsearch::Model::Client
+
+ store::CLIENT_MUTEX.synchronize do
+ config = Gitlab::CurrentSettings.elasticsearch_config
+
+ if store.cached_client.nil? || config != store.cached_config
+ store.cached_client = ::Gitlab::Elastic::Client.build(config)
+ store.cached_config = config
+ end
+ end
+
+ store.cached_client
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/config/initializers/geo.rb b/config/initializers/geo.rb
new file mode 100644
index 00000000000..4cc9fbf49b2
--- /dev/null
+++ b/config/initializers/geo.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+Gitlab.ee do
+ if File.exist?(Rails.root.join('config/database_geo.yml'))
+ Rails.application.configure do
+ config.geo_database = config_for(:database_geo)
+ end
+ end
+
+ begin
+ if Gitlab::Geo.connected? && Gitlab::Geo.primary?
+ Gitlab::Geo.current_node&.update_clone_url!
+ end
+ rescue => e
+ warn "WARNING: Unable to check/update clone_url_prefix for Geo: #{e}"
+ end
+end
diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
index 959daa93f78..9f466dc39de 100644
--- a/config/initializers/health_check.rb
+++ b/config/initializers/health_check.rb
@@ -1,4 +1,10 @@
HealthCheck.setup do |config|
config.standard_checks = %w(database migrations cache)
config.full_checks = %w(database migrations cache)
+
+ Gitlab.ee do
+ config.add_custom_check('geo') do
+ Gitlab::Geo::HealthCheck.new.perform_checks
+ end
+ end
end
diff --git a/config/initializers/httpclient_patch.rb b/config/initializers/httpclient_patch.rb
new file mode 100644
index 00000000000..22cc5605d9b
--- /dev/null
+++ b/config/initializers/httpclient_patch.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# By default, httpclient (and hence anything that uses rack-oauth2)
+# ignores the system-wide SSL certificate configuration in favor of its
+# own cacert.pem. This makes it impossible to use custom certificates
+# without patching that file. Until
+# https://github.com/nahi/httpclient/pull/386 is merged, we work around
+# this limitation by forcing the HTTPClient SSL store to use the default
+# system configuration.
+module HTTPClient::SSLConfigDefaultPaths
+ def initialize(client)
+ super
+
+ set_default_paths
+ end
+end
+
+HTTPClient::SSLConfig.prepend HTTPClient::SSLConfigDefaultPaths
diff --git a/config/initializers/load_balancing.rb b/config/initializers/load_balancing.rb
new file mode 100644
index 00000000000..029c0ff4277
--- /dev/null
+++ b/config/initializers/load_balancing.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# We need to run this initializer after migrations are done so it doesn't fail on CI
+
+Gitlab.ee do
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses')
+ if Gitlab::Database::LoadBalancing.enable?
+ Gitlab::Database.disable_prepared_statements
+
+ Gitlab::Application.configure do |config|
+ config.middleware.use(Gitlab::Database::LoadBalancing::RackMiddleware)
+ end
+
+ Gitlab::Database::LoadBalancing.configure_proxy
+
+ # This needs to be executed after fork of clustered processes
+ Gitlab::Cluster::LifecycleEvents.on_worker_start do
+ # Service discovery must be started after configuring the proxy, as service
+ # discovery depends on this.
+ Gitlab::Database::LoadBalancing.start_service_discovery
+ end
+
+ end
+ end
+end
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 1ad93e14f7e..3d84b4e44ce 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -18,7 +18,7 @@ unless Sidekiq.server?
.map { |k, v| { key: k, value: v } }
payload = {
- time: event.time.utc.iso8601(3),
+ time: Time.now.utc.iso8601(3),
params: params,
remote_ip: event.payload[:remote_ip],
user_id: event.payload[:user_id],
@@ -34,6 +34,13 @@ unless Sidekiq.server?
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
+
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 9a569be7674..00000000000
--- a/config/initializers/mysql_ignore_postgresql_options.rb
+++ /dev/null
@@ -1,49 +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_sql, :add_index_sql
- 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_sql(table_name, column_name, options = {})
- unless options[:opclasses]
- __gitlab_add_index_sql(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/peek.rb b/config/initializers/peek.rb
index d492b60705d..8416ae430c7 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -2,11 +2,7 @@ Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Re
Peek.into Peek::Views::Host
-if Gitlab::Database.mysql?
- require 'peek-mysql2'
- PEEK_DB_CLIENT = ::Mysql2::Client
- PEEK_DB_VIEW = Peek::Views::Mysql2
-elsif Gitlab::Database.postgresql?
+if Gitlab::Database.postgresql?
require 'peek-pg'
PEEK_DB_CLIENT = ::PG::Connection
PEEK_DB_VIEW = Peek::Views::PG
@@ -29,7 +25,8 @@ end
Peek.into PEEK_DB_VIEW
Peek.into Peek::Views::Gitaly
Peek.into Peek::Views::Rblineprof
-Peek.into Peek::Views::Redis
+Peek.into Peek::Views::RedisDetailed
+Peek.into Peek::Views::Rugged
Peek.into Peek::Views::GC
Peek.into Peek::Views::Tracing if Labkit::Tracing.tracing_url_enabled?
diff --git a/config/initializers/postgresql_cte.rb b/config/initializers/postgresql_cte.rb
index 56689bc8e74..68d53c4edbf 100644
--- a/config/initializers/postgresql_cte.rb
+++ b/config/initializers/postgresql_cte.rb
@@ -94,8 +94,8 @@ module ActiveRecord
end
end
- def build_arel
- arel = super()
+ def build_arel(aliases)
+ arel = super
build_with(arel) if @values[:with]
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_timeout.rb b/config/initializers/rack_timeout.rb
index 58f46b55725..246cf3482a4 100644
--- a/config/initializers/rack_timeout.rb
+++ b/config/initializers/rack_timeout.rb
@@ -14,8 +14,8 @@ if defined?(::Puma) && !Rails.env.test?
Gitlab::Application.configure do |config|
config.middleware.insert_before(Rack::Runtime, Rack::Timeout,
- service_timeout: 60,
- wait_timeout: 90)
+ service_timeout: ENV.fetch('GITLAB_RAILS_RACK_TIMEOUT', 60).to_i,
+ wait_timeout: ENV.fetch('GITLAB_RAILS_WAIT_TIMEOUT', 90).to_i)
end
observer = Gitlab::Cluster::RackTimeoutObserver.new
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 7b69cf11288..166fb9b6916 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -72,7 +72,7 @@ Sidekiq.configure_server do |config|
cron_jobs[k]['class'] = cron_jobs[k].delete('job_class')
else
cron_jobs.delete(k)
- Rails.logger.error("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.")
+ Rails.logger.error("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.") # rubocop:disable Gitlab/RailsLogger
end
end
Sidekiq::Cron::Job.load_from_hash! cron_jobs
@@ -83,7 +83,20 @@ Sidekiq.configure_server do |config|
Rails.application.config.database_configuration[Rails.env]
db_config['pool'] = Sidekiq.options[:concurrency]
ActiveRecord::Base.establish_connection(db_config)
- Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
+ Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") # rubocop:disable Gitlab/RailsLogger
+
+ Gitlab.ee do
+ Gitlab::Mirror.configure_cron_job!
+
+ Gitlab::Geo.configure_cron_jobs!
+
+ if Gitlab::Geo.geo_database_configured?
+ Rails.configuration.geo_database['pool'] = Sidekiq.options[:concurrency]
+ Geo::TrackingBase.establish_connection(Rails.configuration.geo_database)
+
+ Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{Geo::TrackingBase.connection_pool.size} (Geo tracking database)") # rubocop:disable Gitlab/RailsLogger
+ end
+ end
# Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
# https://github.com/mikel/mail/issues/912#issuecomment-214850355
diff --git a/config/initializers/sidekiq_cluster.rb b/config/initializers/sidekiq_cluster.rb
new file mode 100644
index 00000000000..baa7495aa29
--- /dev/null
+++ b/config/initializers/sidekiq_cluster.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+if ENV['ENABLE_SIDEKIQ_CLUSTER'] && Gitlab.ee?
+ Thread.new do
+ Thread.current.abort_on_exception = true
+
+ parent = Process.ppid
+
+ loop do
+ sleep(5)
+
+ # In cluster mode it's possible that the master process is SIGKILL'd. In
+ # this case the parent PID changes and we need to terminate ourselves.
+ if Process.ppid != parent
+ Process.kill(:TERM, Process.pid)
+ break
+ end
+ end
+ end
+end
diff --git a/config/initializers/zz_metrics.rb b/config/initializers/zz_metrics.rb
index 5aa6f73c5c5..b005fdf159b 100644
--- a/config/initializers/zz_metrics.rb
+++ b/config/initializers/zz_metrics.rb
@@ -6,6 +6,7 @@
# that we can stub it for testing, as it is only called when metrics are
# enabled.
#
+# rubocop:disable Metrics/AbcSize
def instrument_classes(instrumentation)
instrumentation.instrument_instance_methods(Gitlab::Shell)
@@ -53,7 +54,7 @@ def instrument_classes(instrumentation)
instrumentation.instrument_methods(Banzai::Querying)
instrumentation.instrument_instance_methods(Banzai::ObjectRenderer)
- instrumentation.instrument_instance_methods(Banzai::Redactor)
+ instrumentation.instrument_instance_methods(Banzai::ReferenceRedactor)
[Issuable, Mentionable, Participable].each do |klass|
instrumentation.instrument_instance_methods(klass)
@@ -86,12 +87,42 @@ def instrument_classes(instrumentation)
instrumentation.instrument_methods(Gitlab::Highlight)
instrumentation.instrument_instance_methods(Gitlab::Highlight)
+ Gitlab.ee do
+ instrumentation.instrument_methods(Elasticsearch::Git::Repository)
+ instrumentation.instrument_instance_methods(Elasticsearch::Git::Repository)
+
+ instrumentation.instrument_instance_methods(Search::GlobalService)
+ instrumentation.instrument_instance_methods(Search::ProjectService)
+
+ instrumentation.instrument_instance_methods(Gitlab::Elastic::SearchResults)
+ instrumentation.instrument_instance_methods(Gitlab::Elastic::ProjectSearchResults)
+ instrumentation.instrument_instance_methods(Gitlab::Elastic::Indexer)
+ instrumentation.instrument_instance_methods(Gitlab::Elastic::SnippetSearchResults)
+ instrumentation.instrument_methods(Gitlab::Elastic::Helper)
+
+ instrumentation.instrument_instance_methods(Elastic::ApplicationSearch)
+ instrumentation.instrument_instance_methods(Elastic::IssuesSearch)
+ instrumentation.instrument_instance_methods(Elastic::MergeRequestsSearch)
+ instrumentation.instrument_instance_methods(Elastic::MilestonesSearch)
+ instrumentation.instrument_instance_methods(Elastic::NotesSearch)
+ instrumentation.instrument_instance_methods(Elastic::ProjectsSearch)
+ instrumentation.instrument_instance_methods(Elastic::RepositoriesSearch)
+ instrumentation.instrument_instance_methods(Elastic::SnippetsSearch)
+ instrumentation.instrument_instance_methods(Elastic::WikiRepositoriesSearch)
+
+ instrumentation.instrument_instance_methods(Gitlab::BitbucketImport::Importer)
+ instrumentation.instrument_instance_methods(Bitbucket::Connection)
+
+ instrumentation.instrument_instance_methods(Geo::RepositorySyncWorker)
+ end
+
# This is a Rails scope so we have to instrument it manually.
instrumentation.instrument_method(Project, :visible_to_user)
# Needed for https://gitlab.com/gitlab-org/gitlab-ce/issues/30224#note_32306159
instrumentation.instrument_instance_method(MergeRequestDiff, :load_commits)
end
+# rubocop:enable Metrics/AbcSize
# With prometheus enabled by default this breaks all specs
# that stubs methods using `any_instance_of` for the models reloaded here.
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/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/prometheus/cluster_metrics.yml b/config/prometheus/cluster_metrics.yml
new file mode 100644
index 00000000000..3df76b0974f
--- /dev/null
+++ b/config/prometheus/cluster_metrics.yml
@@ -0,0 +1,63 @@
+- group: Cluster Health
+ priority: 1
+ metrics:
+ - title: "CPU Usage"
+ y_label: "CPU"
+ required_metrics: ['container_cpu_usage_seconds_total']
+ weight: 1
+ queries:
+ - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{id="/"}[15m])) by (job)) without (job)'
+ label: Usage
+ unit: "cores"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_pod_container_resource_requests_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
+ label: Requested
+ unit: "cores"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_node_status_capacity_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
+ label: Capacity
+ unit: "cores"
+ appearance:
+ line:
+ type: 'dashed'
+ width: 2
+ area:
+ opacity: 0
+ - title: "Memory usage"
+ y_label: "Memory"
+ required_metrics: ['container_memory_usage_bytes']
+ weight: 1
+ queries:
+ - query_range: 'avg(sum(container_memory_usage_bytes{id="/"}) by (job)) without (job) / 2^30'
+ label: Usage
+ unit: "GiB"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_pod_container_resource_requests_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
+ label: Requested
+ unit: "GiB"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_node_status_capacity_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
+ label: Capacity
+ unit: "GiB"
+ appearance:
+ line:
+ type: 'dashed'
+ width: 2
+ area:
+ opacity: 0
diff --git a/config/pseudonymizer.yml b/config/pseudonymizer.yml
new file mode 100644
index 00000000000..1d85ac1db45
--- /dev/null
+++ b/config/pseudonymizer.yml
@@ -0,0 +1,475 @@
+tables:
+ approvals:
+ whitelist:
+ - id
+ - merge_request_id
+ - user_id
+ - created_at
+ - updated_at
+ approver_groups:
+ whitelist:
+ - id
+ - target_type
+ - group_id
+ - created_at
+ - updated_at
+ board_assignees:
+ whitelist:
+ - id
+ - board_id
+ - assignee_id
+ board_labels:
+ whitelist:
+ - id
+ - board_id
+ - label_id
+ boards:
+ whitelist:
+ - id
+ - project_id
+ - created_at
+ - updated_at
+ - milestone_id
+ - group_id
+ - weight
+ epic_issues:
+ whitelist:
+ - id
+ - epic_id
+ - issue_id
+ - relative_position
+ epic_metrics:
+ whitelist:
+ - id
+ - epic_id
+ - created_at
+ - updated_at
+ epics:
+ whitelist:
+ - id
+ - milestone_id
+ - group_id
+ - author_id
+ - assignee_id
+ - iid
+ - updated_by_id
+ - last_edited_by_id
+ - lock_version
+ - start_date
+ - end_date
+ - last_edited_at
+ - created_at
+ - updated_at
+ - title
+ - description
+ issue_assignees:
+ whitelist:
+ - user_id
+ - issue_id
+ issue_links:
+ whitelist:
+ - id
+ - source_id
+ - target_id
+ - created_at
+ - updated_at
+ issue_metrics:
+ whitelist:
+ - id
+ - issue_id
+ - first_mentioned_in_commit_at
+ - first_associated_with_milestone_at
+ - first_added_to_board_at
+ - created_at
+ - updated_at
+ issues:
+ whitelist:
+ - id
+ - title
+ - author_id
+ - project_id
+ - created_at
+ - confidential
+ - updated_at
+ - description
+ - milestone_id
+ - state
+ - updated_by_id
+ - weight
+ - due_date
+ - moved_to_id
+ - lock_version
+ - time_estimate
+ - last_edited_at
+ - last_edited_by_id
+ - discussion_locked
+ - closed_at
+ label_links:
+ whitelist:
+ - id
+ - label_id
+ - target_id
+ - target_type
+ - created_at
+ - updated_at
+ label_priorities:
+ whitelist:
+ - id
+ - project_id
+ - label_id
+ - priority
+ - created_at
+ - updated_at
+ labels:
+ whitelist:
+ - id
+ - title
+ - color
+ - project_id
+ - created_at
+ - updated_at
+ - template
+ - type
+ - group_id
+ licenses:
+ whitelist:
+ - id
+ - created_at
+ - updated_at
+ merge_request_diffs:
+ whitelist:
+ - id
+ - state
+ - merge_request_id
+ - created_at
+ - updated_at
+ - base_commit_sha
+ - real_size
+ - head_commit_sha
+ - start_commit_sha
+ - commits_count
+ merge_request_metrics:
+ whitelist:
+ - id
+ - merge_request_id
+ - latest_build_started_at
+ - latest_build_finished_at
+ - first_deployed_to_production_at
+ - merged_at
+ - created_at
+ - updated_at
+ - pipeline_id
+ - merged_by_id
+ - latest_closed_by_id
+ - latest_closed_at
+ merge_requests:
+ whitelist:
+ - id
+ - target_branch
+ - source_branch
+ - source_project_id
+ - author_id
+ - assignee_id
+ - created_at
+ - updated_at
+ - milestone_id
+ - state
+ - merge_status
+ - target_project_id
+ - updated_by_id
+ - merge_error
+ - merge_params
+ - merge_when_pipeline_succeeds
+ - merge_user_id
+ - approvals_before_merge
+ - lock_version
+ - time_estimate
+ - squash
+ - last_edited_at
+ - last_edited_by_id
+ - head_pipeline_id
+ - discussion_locked
+ - latest_merge_request_diff_id
+ - allow_maintainer_to_push
+ merge_requests_closing_issues:
+ whitelist:
+ - id
+ - merge_request_id
+ - issue_id
+ - created_at
+ - updated_at
+ milestones:
+ whitelist:
+ - id
+ - project_id
+ - due_date
+ - created_at
+ - updated_at
+ - state
+ - start_date
+ - group_id
+ namespace_statistics:
+ whitelist:
+ - id
+ - namespace_id
+ - shared_runners_seconds
+ - shared_runners_seconds_last_reset
+ namespaces:
+ whitelist:
+ - id
+ - name
+ - path
+ - owner_id
+ - created_at
+ - updated_at
+ - type
+ - avatar
+ - membership_lock
+ - share_with_group_lock
+ - visibility_level
+ - request_access_enabled
+ - ldap_sync_status
+ - ldap_sync_error
+ - ldap_sync_last_update_at
+ - ldap_sync_last_successful_update_at
+ - ldap_sync_last_sync_at
+ - lfs_enabled
+ - parent_id
+ - shared_runners_minutes_limit
+ - repository_size_limit
+ - require_two_factor_authentication
+ - two_factor_grace_period
+ - plan_id
+ - project_creation_level
+ members:
+ whitelist:
+ - id
+ - access_level
+ - source_id
+ - source_type
+ - user_id
+ - notification_level
+ - type
+ - created_by_id
+ - invite_email
+ - invite_accepted_at
+ - requested_at
+ - expires_at
+ - ldap
+ - override
+ notification_settings:
+ whitelist:
+ - id
+ - user_id
+ - source_id
+ - source_type
+ - level
+ - created_at
+ - updated_at
+ - new_note
+ - new_issue
+ - reopen_issue
+ - close_issue
+ - reassign_issue
+ - new_merge_request
+ - reopen_merge_request
+ - close_merge_request
+ - reassign_merge_request
+ - merge_merge_request
+ - failed_pipeline
+ - success_pipeline
+ project_authorizations:
+ whitelist:
+ - user_id
+ - project_id
+ - access_level
+ project_auto_devops:
+ whitelist:
+ - id
+ - project_id
+ - created_at
+ - updated_at
+ - enabled
+ project_custom_attributes:
+ whitelist:
+ - id
+ - created_at
+ - updated_at
+ - project_id
+ - key
+ - value
+ project_features:
+ whitelist:
+ - id
+ - project_id
+ - merge_requests_access_level
+ - issues_access_level
+ - wiki_access_level
+ - snippets_access_level
+ - builds_access_level
+ - created_at
+ - updated_at
+ - repository_access_level
+ project_group_links:
+ whitelist:
+ - id
+ - project_id
+ - group_id
+ - created_at
+ - updated_at
+ - group_access
+ - expires_at
+ project_import_data:
+ whitelist:
+ - id
+ - project_id
+ project_mirror_data:
+ whitelist:
+ - id
+ - project_id
+ - retry_count
+ - last_update_started_at
+ - last_update_scheduled_at
+ - next_execution_timestamp
+ project_repository_states:
+ whitelist:
+ - id
+ - project_id
+ - repository_verification_checksum
+ - wiki_verification_checksum
+ - last_repository_verification_failure
+ - last_wiki_verification_failure
+ project_statistics:
+ whitelist:
+ - id
+ - project_id
+ - namespace_id
+ - commit_count
+ - storage_size
+ - repository_size
+ - lfs_objects_size
+ - build_artifacts_size
+ - shared_runners_seconds
+ - shared_runners_seconds_last_reset
+ projects:
+ whitelist:
+ - id
+ - name
+ - path
+ - description
+ - created_at
+ - updated_at
+ - creator_id
+ - namespace_id
+ - last_activity_at
+ - import_url
+ - visibility_level
+ - archived
+ - avatar
+ - merge_requests_template
+ - star_count
+ - merge_requests_rebase_enabled
+ - import_type
+ - import_source
+ - approvals_before_merge
+ - reset_approvals_on_push
+ - merge_requests_ff_only_enabled
+ - issues_template
+ - mirror
+ - mirror_user_id
+ - shared_runners_enabled
+ - build_coverage_regex
+ - build_allow_git_fetch
+ - build_timeout
+ - mirror_trigger_builds
+ - pending_delete
+ - public_builds
+ - last_repository_check_failed
+ - last_repository_check_at
+ - container_registry_enabled
+ - only_allow_merge_if_pipeline_succeeds
+ - has_external_issue_tracker
+ - repository_storage
+ - repository_read_only
+ - request_access_enabled
+ - has_external_wiki
+ - ci_config_path
+ - lfs_enabled
+ - only_allow_merge_if_all_discussions_are_resolved
+ - repository_size_limit
+ - printing_merge_request_link_enabled
+ - auto_cancel_pending_pipelines
+ - service_desk_enabled
+ - delete_error
+ - last_repository_updated_at
+ - disable_overriding_approvers_per_merge_request
+ - storage_version
+ - resolve_outdated_diff_discussions
+ - remote_mirror_available_overridden
+ - only_mirror_protected_branches
+ - pull_mirror_available_overridden
+ - mirror_overwrites_diverged_branches
+ - external_authorization_classification_label
+ subscriptions:
+ whitelist:
+ - id
+ - user_id
+ - subscribable_id
+ - subscribable_type
+ - subscribed
+ - created_at
+ - updated_at
+ - project_id
+ users:
+ whitelist:
+ - id
+ - remember_created_at
+ - sign_in_count
+ - current_sign_in_at
+ - last_sign_in_at
+ - current_sign_in_ip
+ - last_sign_in_ip
+ - created_at
+ - updated_at
+ - admin
+ - projects_limit
+ - failed_attempts
+ - locked_at
+ - can_create_group
+ - can_create_team
+ - state
+ - color_scheme_id
+ - password_expires_at
+ - created_by_id
+ - last_credential_check_at
+ - avatar
+ - confirmed_at
+ - confirmation_sent_at
+ - unconfirmed_email
+ - hide_no_ssh_key
+ - website_url
+ - admin_email_unsubscribed_at
+ - notification_email
+ - hide_no_password
+ - password_automatically_set
+ - location
+ - public_email
+ - dashboard
+ - project_view
+ - consumed_timestep
+ - layout
+ - hide_project_limit
+ - note
+ - otp_grace_period_started_at
+ - external
+ - organization
+ - auditor
+ - require_two_factor_authentication_from_group
+ - two_factor_grace_period
+ - ghost
+ - last_activity_on
+ - notified_of_own_activity
+ - bot_type
+ - preferred_language
+ - theme_id
+
diff --git a/config/routes.rb b/config/routes.rb
index a42fc037227..459f2b22bf0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -82,7 +82,11 @@ Rails.application.routes.draw do
resources :issues, only: [:index, :create, :update]
end
- resources :issues, module: :boards, only: [:index, :update]
+ resources :issues, module: :boards, only: [:index, :update] do
+ collection do
+ put :bulk_move, format: :json
+ end
+ end
Gitlab.ee do
resources :users, module: :boards, only: [:index]
@@ -106,11 +110,20 @@ Rails.application.routes.draw do
draw :jira_connect
end
- if ENV['GITLAB_ENABLE_CHAOS_ENDPOINTS']
- get '/chaos/leakmem' => 'chaos#leakmem'
- get '/chaos/cpuspin' => 'chaos#cpuspin'
- get '/chaos/sleep' => 'chaos#sleep'
- get '/chaos/kill' => 'chaos#kill'
+ Gitlab.ee do
+ constraints(::Constraints::FeatureConstrainer.new(:analytics)) do
+ draw :analytics
+ end
+ end
+
+ if ENV['GITLAB_CHAOS_SECRET'] || Rails.env.development? || Rails.env.test?
+ resource :chaos, only: [] do
+ get :leakmem
+ get :cpu_spin
+ get :db_spin
+ get :sleep
+ get :kill
+ end
end
end
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/api.rb b/config/routes/api.rb
index 3ba9176d943..d55bbdf6776 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -1,7 +1,5 @@
-constraints(::Constraints::FeatureConstrainer.new(:graphql, default_enabled: true)) do
- post '/api/graphql', to: 'graphql#execute'
- mount GraphiQL::Rails::Engine, at: '/-/graphql-explorer', graphql_path: '/api/graphql'
-end
+post '/api/graphql', to: 'graphql#execute'
+mount GraphiQL::Rails::Engine, at: '/-/graphql-explorer', graphql_path: '/api/graphql'
-::API::API.logger Rails.logger
+::API::API.logger Rails.logger # rubocop:disable Gitlab/RailsLogger
mount ::API::API => '/'
diff --git a/config/settings.rb b/config/settings.rb
index 6df2132332c..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,
@@ -62,6 +63,31 @@ class Settings < Settingslogic
(base_url(gitlab) + [gitlab.relative_url_root]).join('')
end
+ def kerberos_protocol
+ kerberos.https ? "https" : "http"
+ end
+
+ def kerberos_port
+ kerberos.use_dedicated_port ? kerberos.port : gitlab.port
+ end
+
+ # Curl expects username/password for authentication. However when using GSS-Negotiate not credentials should be needed.
+ # By inserting in the Kerberos dedicated URL ":@", we give to curl an empty username and password and GSS auth goes ahead
+ # Known bug reported in http://sourceforge.net/p/curl/bugs/440/ and http://curl.haxx.se/docs/knownbugs.html
+ def build_gitlab_kerberos_url
+ [
+ kerberos_protocol,
+ "://:@",
+ gitlab.host,
+ ":#{kerberos_port}",
+ gitlab.relative_url_root
+ ].join('')
+ end
+
+ def alternative_gitlab_kerberos_url?
+ kerberos.enabled && (build_gitlab_kerberos_url != build_gitlab_url)
+ end
+
# check that values in `current` (string or integer) is a contant in `modul`.
def verify_constant_array(modul, current, default)
values = default || []
@@ -145,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..c7586aa1e38 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]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index a81590e8b8e..cd793743eb7 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -344,6 +344,8 @@ module.exports = {
devtool: NO_SOURCEMAPS ? false : devtool,
- // sqljs requires fs
- node: { fs: 'empty' },
+ node: {
+ fs: 'empty', // sqljs requires fs
+ setImmediate: false,
+ },
};
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index ec494635f02..0c675cc4c9c 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -88,6 +88,19 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize
# We ignore revert commits as they are well structured by Git already
return false if commit.message.start_with?('Revert "')
+ # Fail if a suggestion commit is used and squash is not enabled
+ if commit.message.start_with?('Apply suggestion to')
+ if gitlab.mr_json['squash']
+ return false
+ else
+ fail_commit(
+ commit,
+ 'If you are applying suggestions, enable squash in the merge request and re-run the failed job'
+ )
+ return true
+ end
+ end
+
failures = false
subject, separator, details = commit.message.split("\n", 3)
@@ -114,16 +127,6 @@ def lint_commit(commit) # rubocop:disable Metrics/AbcSize
)
end
- # Fail if a suggestion commit is used and squash is not enabled
- if commit.message.start_with?('Apply suggestion to') && !gitlab.mr_json['squash']
- fail_commit(
- commit,
- 'If you are applying suggestions, squash needs to be enabled in the merge request'
- )
-
- failures = true
- end
-
unless subject_starts_with_capital?(subject)
fail_commit(commit, 'The commit subject must start with a capital letter')
failures = true
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/metadata/Dangerfile b/danger/metadata/Dangerfile
index 1adca152736..f2d68e64eb6 100644
--- a/danger/metadata/Dangerfile
+++ b/danger/metadata/Dangerfile
@@ -1,5 +1,13 @@
# rubocop:disable Style/SignalException
+THROUGHPUT_LABELS = [
+ 'Community contribution',
+ 'security',
+ 'bug',
+ 'feature',
+ 'backstage'
+].freeze
+
if gitlab.mr_body.size < 5
fail "Please provide a proper merge request description."
end
@@ -8,6 +16,10 @@ if gitlab.mr_labels.empty?
fail "Please add labels to this merge request."
end
+if (THROUGHPUT_LABELS & gitlab.mr_labels).empty?
+ warn 'Please add a [throughput label](https://about.gitlab.com/handbook/engineering/management/throughput/#implementation) to this merge request.'
+end
+
unless gitlab.mr_json["assignee"]
warn "This merge request does not have any assignee yet. Setting an assignee clarifies who needs to take action on the merge request at any given time."
end
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..05bda7d3672 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -79,9 +79,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 +106,7 @@ class Gitlab::Seeder::Pipelines
end
pipelines.flatten
- rescue
+ rescue ActiveRecord::ActiveRecordError
[]
end
@@ -231,6 +239,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
diff --git a/db/fixtures/development/99_common_metrics.rb b/db/fixtures/development/99_common_metrics.rb
index 1f39c0ce5a0..d52f78ea536 100644
--- a/db/fixtures/development/99_common_metrics.rb
+++ b/db/fixtures/development/99_common_metrics.rb
@@ -1,5 +1,3 @@
# frozen_string_literal: true
-require Rails.root.join('db/importers/common_metrics_importer.rb')
-
-::Importers::CommonMetricsImporter.new.execute
+::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
diff --git a/db/fixtures/production/999_common_metrics.rb b/db/fixtures/production/999_common_metrics.rb
index 1f39c0ce5a0..d52f78ea536 100644
--- a/db/fixtures/production/999_common_metrics.rb
+++ b/db/fixtures/production/999_common_metrics.rb
@@ -1,5 +1,3 @@
# frozen_string_literal: true
-require Rails.root.join('db/importers/common_metrics_importer.rb')
-
-::Importers::CommonMetricsImporter.new.execute
+::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
diff --git a/db/importers/common_metrics_importer.rb b/db/importers/common_metrics_importer.rb
deleted file mode 100644
index 195bde8f34a..00000000000
--- a/db/importers/common_metrics_importer.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-# frozen_string_literal: true
-
-module Importers
- class PrometheusMetric < ActiveRecord::Base
- enum group: {
- # built-in groups
- nginx_ingress_vts: -1,
- ha_proxy: -2,
- aws_elb: -3,
- nginx: -4,
- kubernetes: -5,
- nginx_ingress: -6,
-
- # custom groups
- business: 0,
- response: 1,
- system: 2
- }
-
- scope :common, -> { where(common: true) }
-
- GROUP_TITLES = {
- business: _('Business metrics (Custom)'),
- response: _('Response metrics (Custom)'),
- system: _('System metrics (Custom)'),
- nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'),
- nginx_ingress: _('Response metrics (NGINX Ingress)'),
- ha_proxy: _('Response metrics (HA Proxy)'),
- aws_elb: _('Response metrics (AWS ELB)'),
- nginx: _('Response metrics (NGINX)'),
- kubernetes: _('System metrics (Kubernetes)')
- }.freeze
- end
-
- class CommonMetricsImporter
- MissingQueryId = Class.new(StandardError)
-
- attr_reader :content
-
- def initialize(filename = 'common_metrics.yml')
- @content = YAML.load_file(Rails.root.join('config', 'prometheus', filename))
- end
-
- def execute
- PrometheusMetric.reset_column_information
-
- process_content do |id, attributes|
- find_or_build_metric!(id)
- .update!(**attributes)
- end
- end
-
- private
-
- def process_content(&blk)
- content['panel_groups'].map do |group|
- process_group(group, &blk)
- end
- end
-
- def process_group(group, &blk)
- attributes = {
- group: find_group_title_key(group['group'])
- }
-
- group['panels'].map do |panel|
- process_panel(panel, attributes, &blk)
- end
- end
-
- def process_panel(panel, attributes, &blk)
- attributes = attributes.merge(
- title: panel['title'],
- y_label: panel['y_label'])
-
- panel['metrics'].map do |metric_details|
- process_metric_details(metric_details, attributes, &blk)
- end
- end
-
- def process_metric_details(metric_details, attributes, &blk)
- attributes = attributes.merge(
- legend: metric_details['label'],
- query: metric_details['query_range'],
- unit: metric_details['unit'])
-
- yield(metric_details['id'], attributes)
- end
-
- def find_or_build_metric!(id)
- raise MissingQueryId unless id
-
- PrometheusMetric.common.find_by(identifier: id) ||
- PrometheusMetric.new(common: true, identifier: id)
- end
-
- def find_group_title_key(title)
- PrometheusMetric.groups[find_group_title(title)]
- end
-
- def find_group_title(title)
- PrometheusMetric::GROUP_TITLES.invert[title]
- end
- end
-end
diff --git a/db/migrate/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/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/20180831164910_import_common_metrics.rb b/db/migrate/20180831164910_import_common_metrics.rb
index f67d5f40aad..4e61a25c1ad 100644
--- a/db/migrate/20180831164910_import_common_metrics.rb
+++ b/db/migrate/20180831164910_import_common_metrics.rb
@@ -3,12 +3,10 @@
class ImportCommonMetrics < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb
index 5cd312837df..2b238774dca 100644
--- a/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb
+++ b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb
@@ -1,12 +1,10 @@
class ImportCommonMetricsNginxVts < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/db/migrate/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/20190326164045_import_common_metrics_knative.rb b/db/migrate/20190326164045_import_common_metrics_knative.rb
index 340ec1e1f75..6b331755774 100644
--- a/db/migrate/20190326164045_import_common_metrics_knative.rb
+++ b/db/migrate/20190326164045_import_common_metrics_knative.rb
@@ -3,12 +3,10 @@
class ImportCommonMetricsKnative < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/db/migrate/20190408163745_prometheus_knative05_fix.rb b/db/migrate/20190408163745_prometheus_knative05_fix.rb
index c11f6f0e29b..3d0aa782669 100644
--- a/db/migrate/20190408163745_prometheus_knative05_fix.rb
+++ b/db/migrate/20190408163745_prometheus_knative05_fix.rb
@@ -6,12 +6,10 @@
class PrometheusKnative05Fix < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/db/migrate/20190620105427_change_null_private_profile_to_false.rb b/db/migrate/20190620105427_change_null_private_profile_to_false.rb
new file mode 100644
index 00000000000..d820dbbe9d3
--- /dev/null
+++ b/db/migrate/20190620105427_change_null_private_profile_to_false.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+class ChangeNullPrivateProfileToFalse < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ DELAY = 30.seconds.to_i
+ BATCH_SIZE = 1_000
+
+ disable_ddl_transaction!
+
+ class User < ActiveRecord::Base
+ self.table_name = 'users'
+
+ include ::EachBatch
+ end
+
+ def up
+ change_column_default :users, :private_profile, false
+
+ # Migration will take about 120 hours
+ User.where(private_profile: nil).each_batch(of: BATCH_SIZE) do |batch, index|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+ delay = index * DELAY
+
+ BackgroundMigrationWorker.perform_in(delay.seconds, 'MigrateNullPrivateProfileToFalse', [*range])
+ end
+ end
+
+ def down
+ change_column_default :users, :private_profile, nil
+ 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/20190709204413_add_rule_type_to_approval_project_rules.rb b/db/migrate/20190709204413_add_rule_type_to_approval_project_rules.rb
new file mode 100644
index 00000000000..87a228c9bf9
--- /dev/null
+++ b/db/migrate/20190709204413_add_rule_type_to_approval_project_rules.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddRuleTypeToApprovalProjectRules < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :approval_project_rules, :rule_type, :integer, limit: 2, default: 0, allow_null: false
+ end
+
+ def down
+ remove_column :approval_project_rules, :rule_type
+ 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/20190710151229_add_index_to_approval_project_rules_rule_type.rb b/db/migrate/20190710151229_add_index_to_approval_project_rules_rule_type.rb
new file mode 100644
index 00000000000..64123c53c4a
--- /dev/null
+++ b/db/migrate/20190710151229_add_index_to_approval_project_rules_rule_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToApprovalProjectRulesRuleType < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :approval_project_rules, :rule_type
+ end
+
+ def down
+ remove_concurrent_index :approval_project_rules, :rule_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/20180223124427_build_user_interacted_projects_table.rb b/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
index fa332fd5c70..325895a5ddb 100644
--- a/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
+++ b/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
@@ -82,7 +82,7 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration[4.2]
iteration = 0
records = 0
begin
- Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}"
+ Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}" # rubocop:disable Gitlab/RailsLogger
result = execute <<~SQL
INSERT INTO user_interacted_projects (user_id, project_id)
SELECT e.user_id, e.project_id
@@ -93,7 +93,7 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration[4.2]
SQL
iteration += 1
records += result.cmd_tuples
- Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall"
+ Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall" # rubocop:disable Gitlab/RailsLogger
Kernel.sleep(SLEEP_TIME) if result.cmd_tuples > 0
end while result.cmd_tuples > 0
end
diff --git a/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/20190628191740_schedule_fixing_names_of_user_namespaces.rb b/db/post_migrate/20190628191740_schedule_fixing_names_of_user_namespaces.rb
new file mode 100644
index 00000000000..8fa7068b957
--- /dev/null
+++ b/db/post_migrate/20190628191740_schedule_fixing_names_of_user_namespaces.rb
@@ -0,0 +1,48 @@
+# 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 ScheduleFixingNamesOfUserNamespaces < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ class Namespace < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'namespaces'
+
+ scope :user_namespaces, -> { where(type: nil) }
+ end
+
+ class Route < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'routes'
+
+ scope :project_routes, -> { where(source_type: 'Project') }
+ end
+
+ disable_ddl_transaction!
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ ScheduleFixingNamesOfUserNamespaces::Namespace.user_namespaces,
+ 'FixUserNamespaceNames',
+ 60.seconds,
+ batch_size: 5000
+ )
+
+ queue_background_migration_jobs_by_range_at_intervals(
+ ScheduleFixingNamesOfUserNamespaces::Route.project_routes,
+ 'FixUserProjectRouteNames',
+ 60.seconds,
+ batch_size: 5000
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20190702173936_populate_remaining_merge_request_assignees.rb b/db/post_migrate/20190702173936_populate_remaining_merge_request_assignees.rb
new file mode 100644
index 00000000000..c435b94015d
--- /dev/null
+++ b/db/post_migrate/20190702173936_populate_remaining_merge_request_assignees.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 PopulateRemainingMergeRequestAssignees < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10_000
+ MIGRATION = 'PopulateMergeRequestAssigneesTable'
+
+ disable_ddl_transaction!
+
+ def up
+ Gitlab::BackgroundMigration.steal(MIGRATION)
+
+ Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable.new.perform_all_sync(batch_size: BATCH_SIZE)
+ end
+end
diff --git a/db/post_migrate/20190703185326_fix_wrong_pages_access_level.rb b/db/post_migrate/20190703185326_fix_wrong_pages_access_level.rb
new file mode 100644
index 00000000000..e5981956cf5
--- /dev/null
+++ b/db/post_migrate/20190703185326_fix_wrong_pages_access_level.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class FixWrongPagesAccessLevel < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ MIGRATION = 'FixPagesAccessLevel'
+ BATCH_SIZE = 20_000
+ BATCH_TIME = 2.minutes
+
+ disable_ddl_transaction!
+
+ class ProjectFeature < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'project_features'
+ self.inheritance_column = :_type_disabled
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ ProjectFeature,
+ MIGRATION,
+ BATCH_TIME,
+ batch_size: BATCH_SIZE)
+ end
+end
diff --git a/db/post_migrate/20190715114644_drop_project_features_pages_access_level_default.rb b/db/post_migrate/20190715114644_drop_project_features_pages_access_level_default.rb
new file mode 100644
index 00000000000..2fb0aa0f460
--- /dev/null
+++ b/db/post_migrate/20190715114644_drop_project_features_pages_access_level_default.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class DropProjectFeaturesPagesAccessLevelDefault < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ ENABLED_VALUE = 20
+
+ def change
+ change_column_default :project_features, :pages_access_level, from: ENABLED_VALUE, to: nil
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9a8b64689bd..f42a4c600ea 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,11 +10,11 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190703130053) do
+ActiveRecord::Schema.define(version: 2019_07_15_114644) do
# These are extensions that must be enabled in order to support this database
- enable_extension "plpgsql"
enable_extension "pg_trgm"
+ enable_extension "plpgsql"
create_table "abuse_reports", id: :serial, force: :cascade do |t|
t.integer "reporter_id"
@@ -63,6 +63,7 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.datetime "updated_at"
t.string "home_page_url"
t.integer "default_branch_protection", default: 2
+ t.text "help_text"
t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true
t.integer "max_attachment_size", default: 10, null: false
@@ -104,6 +105,8 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.integer "container_registry_token_expire_delay", default: 5
t.text "after_sign_up_text"
t.boolean "user_default_external", default: false, null: false
+ t.boolean "elasticsearch_indexing", default: false, null: false
+ t.boolean "elasticsearch_search", default: false, null: false
t.string "repository_storages", default: "default"
t.string "enabled_git_access_protocol"
t.boolean "domain_blacklist_enabled", default: false
@@ -125,18 +128,37 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.boolean "html_emails_enabled", default: true
t.string "plantuml_url"
t.boolean "plantuml_enabled"
+ t.integer "shared_runners_minutes", default: 0, null: false
+ t.bigint "repository_size_limit", default: 0
t.integer "terminal_max_session_time", default: 0, null: false
t.integer "unique_ips_limit_per_user"
t.integer "unique_ips_limit_time_window"
t.boolean "unique_ips_limit_enabled", default: false, null: false
t.string "default_artifacts_expire_in", default: "0", null: false
+ t.string "elasticsearch_url", default: "http://localhost:9200"
+ t.boolean "elasticsearch_aws", default: false, null: false
+ t.string "elasticsearch_aws_region", default: "us-east-1"
+ t.string "elasticsearch_aws_access_key"
+ t.string "elasticsearch_aws_secret_access_key"
+ t.integer "geo_status_timeout", default: 10
t.string "uuid"
t.decimal "polling_interval_multiplier", default: "1.0", null: false
+ t.boolean "elasticsearch_experimental_indexer"
t.integer "cached_markdown_version"
+ t.boolean "check_namespace_plan", default: false, null: false
+ t.integer "mirror_max_delay", default: 300, null: false
+ t.integer "mirror_max_capacity", default: 100, null: false
+ t.integer "mirror_capacity_threshold", default: 50, null: false
t.boolean "prometheus_metrics_enabled", default: true, null: false
+ t.boolean "authorized_keys_enabled", default: true, null: false
t.boolean "help_page_hide_commercial_content", default: false
t.string "help_page_support_url"
+ t.boolean "slack_app_enabled", default: false
+ t.string "slack_app_id"
+ t.string "slack_app_secret"
+ t.string "slack_app_verification_token"
t.integer "performance_bar_allowed_group_id"
+ t.boolean "allow_group_owners_to_manage_ldap", default: true, null: false
t.boolean "hashed_storage_enabled", default: true, null: false
t.boolean "project_export_enabled", default: true, null: false
t.boolean "auto_devops_enabled", default: true, null: false
@@ -149,22 +171,38 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.boolean "throttle_authenticated_web_enabled", default: false, null: false
t.integer "throttle_authenticated_web_requests_per_period", default: 7200, null: false
t.integer "throttle_authenticated_web_period_in_seconds", default: 3600, null: false
- t.boolean "password_authentication_enabled_for_web"
- t.boolean "password_authentication_enabled_for_git", default: true, null: false
t.integer "gitaly_timeout_default", default: 55, null: false
t.integer "gitaly_timeout_medium", default: 30, null: false
t.integer "gitaly_timeout_fast", default: 10, null: false
- t.boolean "authorized_keys_enabled", default: true, null: false
+ t.boolean "mirror_available", default: true, null: false
+ t.boolean "password_authentication_enabled_for_web"
+ t.boolean "password_authentication_enabled_for_git", default: true, null: false
t.string "auto_devops_domain"
+ t.boolean "external_authorization_service_enabled", default: false, null: false
+ t.string "external_authorization_service_url"
+ 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"
+ t.string "encrypted_external_auth_client_key_iv"
+ t.string "encrypted_external_auth_client_key_pass"
+ t.string "encrypted_external_auth_client_key_pass_iv"
+ t.string "email_additional_text"
t.boolean "enforce_terms", default: false
- t.boolean "mirror_available", default: true, null: false
+ t.integer "file_template_project_id"
+ 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
t.boolean "web_ide_clientside_preview_enabled", default: false, null: false
t.boolean "user_show_add_ssh_key_message", default: true, null: false
+ t.integer "custom_project_templates_group_id"
t.integer "usage_stats_set_by_user_id"
t.integer "receive_max_input_size"
t.integer "diff_max_patch_bytes", default: 102400, null: false
@@ -174,18 +212,11 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.string "runners_registration_token_encrypted"
t.integer "local_markdown_version", default: 0, null: false
t.integer "first_day_of_week", default: 0, null: false
+ t.boolean "elasticsearch_limit_indexing", default: false, null: false
t.integer "default_project_creation", default: 2, null: false
- t.boolean "external_authorization_service_enabled", default: false, null: false
- t.string "external_authorization_service_url"
- t.string "external_authorization_service_default_label"
- t.float "external_authorization_service_timeout", default: 0.5
- t.text "external_auth_client_cert"
- t.text "encrypted_external_auth_client_key"
- t.string "encrypted_external_auth_client_key_iv"
- t.string "encrypted_external_auth_client_key_pass"
- t.string "encrypted_external_auth_client_key_pass_iv"
t.string "lets_encrypt_notification_email"
t.boolean "lets_encrypt_terms_of_service_accepted", default: false, null: false
+ t.string "geo_node_allowed_ips", default: "0.0.0.0/0, ::/0"
t.integer "elasticsearch_shards", default: 5, null: false
t.integer "elasticsearch_replicas", default: 1, null: false
t.text "encrypted_lets_encrypt_private_key"
@@ -195,49 +226,18 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.boolean "default_project_deletion_protection", default: false, null: false
t.boolean "grafana_enabled", default: false, null: false
t.boolean "lock_memberships_to_ldap", default: false, null: false
- t.text "help_text"
- t.boolean "elasticsearch_indexing", default: false, null: false
- t.boolean "elasticsearch_search", default: false, null: false
- t.integer "shared_runners_minutes", default: 0, null: false
- t.bigint "repository_size_limit", default: 0
- t.string "elasticsearch_url", default: "http://localhost:9200"
- t.boolean "elasticsearch_aws", default: false, null: false
- t.string "elasticsearch_aws_region", default: "us-east-1"
- t.string "elasticsearch_aws_access_key"
- t.string "elasticsearch_aws_secret_access_key"
- t.integer "geo_status_timeout", default: 10
- t.boolean "elasticsearch_experimental_indexer"
- t.boolean "check_namespace_plan", default: false, null: false
- t.integer "mirror_max_delay", default: 300, null: false
- t.integer "mirror_max_capacity", default: 100, null: false
- t.integer "mirror_capacity_threshold", default: 50, null: false
- t.boolean "slack_app_enabled", default: false
- t.string "slack_app_id"
- t.string "slack_app_secret"
- t.string "slack_app_verification_token"
- t.boolean "allow_group_owners_to_manage_ldap", default: true, null: false
- t.string "email_additional_text"
- t.integer "file_template_project_id"
- t.boolean "pseudonymizer_enabled", 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.integer "custom_project_templates_group_id"
- t.boolean "elasticsearch_limit_indexing", default: false, null: false
- t.string "geo_node_allowed_ips", default: "0.0.0.0/0, ::/0"
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.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 ["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 +249,31 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -282,21 +282,23 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.integer "project_id", null: false
t.integer "approvals_required", limit: 2, default: 0, null: false
t.string "name", null: false
- t.index ["project_id"], name: "index_approval_project_rules_on_project_id", using: :btree
+ t.integer "rule_type", limit: 2, default: 0, null: false
+ 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|
@@ -304,8 +306,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -314,8 +316,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -324,8 +326,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -336,8 +338,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -347,8 +349,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -359,15 +361,15 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -376,17 +378,17 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -395,23 +397,23 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
t.integer "project_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.integer "group_id"
+ t.string "name", default: "Development", null: false
t.integer "milestone_id"
+ t.integer "group_id"
t.integer "weight"
- t.string "name", default: "Development", null: false
- 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|
@@ -424,7 +426,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -437,8 +439,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -447,7 +449,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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_trace_chunks", force: :cascade do |t|
@@ -455,13 +457,13 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -472,9 +474,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -524,28 +526,28 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -555,8 +557,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -564,7 +566,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -579,7 +581,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -595,18 +597,18 @@ ActiveRecord::Schema.define(version: 20190703130053) 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_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|
@@ -619,7 +621,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -633,9 +635,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -646,7 +648,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -675,25 +677,25 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -701,8 +703,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -725,12 +727,12 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -739,11 +741,11 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -755,10 +757,10 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -767,8 +769,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -779,8 +781,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -794,14 +796,14 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -817,7 +819,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -825,8 +827,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -844,7 +846,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -859,8 +861,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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|
@@ -871,7 +873,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -884,7 +886,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -898,7 +900,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -910,8 +912,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -924,7 +926,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -937,7 +939,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -949,8 +951,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -963,10 +965,10 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.index ["cluster_id", "namespace"], name: "kubernetes_namespaces_cluster_and_namespace", 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 ["project_id"], name: "index_clusters_kubernetes_namespaces_on_project_id"
end
create_table "container_repositories", id: :serial, force: :cascade do |t|
@@ -974,8 +976,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1021,7 +1023,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1029,7 +1031,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1038,7 +1040,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1050,8 +1052,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1070,37 +1072,37 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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 ["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.index ["sha"], name: "index_design_management_versions_on_sha", unique: true
end
create_table "draft_notes", force: :cascade do |t|
@@ -1112,23 +1114,23 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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|
@@ -1139,9 +1141,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1153,24 +1155,24 @@ ActiveRecord::Schema.define(version: 20190703130053) 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
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.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|
@@ -1203,15 +1205,15 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1222,12 +1224,12 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1236,29 +1238,29 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1266,7 +1268,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1287,25 +1289,25 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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 ["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|
@@ -1317,20 +1319,20 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1338,9 +1340,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1389,7 +1391,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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|
@@ -1409,14 +1411,14 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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|
@@ -1425,7 +1427,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1434,7 +1436,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1446,7 +1448,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1457,13 +1459,13 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1472,7 +1474,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1486,17 +1488,17 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1506,9 +1508,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1522,11 +1524,11 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1535,8 +1537,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1554,8 +1556,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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 ["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|
@@ -1563,8 +1565,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1576,14 +1578,14 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1591,23 +1593,23 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1615,9 +1617,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1627,7 +1629,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1640,7 +1642,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1671,21 +1673,22 @@ ActiveRecord::Schema.define(version: 20190703130053) 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", "state", "relative_position", "id"], name: "index_issues_on_project_id_and_state_and_rel_position_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|
@@ -1693,7 +1696,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1701,9 +1704,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1719,7 +1722,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1732,8 +1735,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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 ["user_id"], name: "index_keys_on_user_id"
end
create_table "label_links", id: :serial, force: :cascade do |t|
@@ -1742,8 +1745,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1752,9 +1755,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1769,11 +1772,11 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1791,8 +1794,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1802,8 +1805,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1812,8 +1815,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1831,11 +1834,11 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1855,20 +1858,20 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1876,8 +1879,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1891,8 +1894,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1910,7 +1913,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1926,8 +1929,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -1942,13 +1945,13 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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|
@@ -1990,26 +1993,26 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2017,8 +2020,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2029,10 +2032,10 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2049,16 +2052,16 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2069,14 +2072,14 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2117,24 +2120,25 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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|
@@ -2147,7 +2151,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2175,16 +2179,16 @@ ActiveRecord::Schema.define(version: 20190703130053) 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 ["noteable_type"], name: "index_notes_on_noteable_type"
+ 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|
@@ -2210,9 +2214,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2224,7 +2228,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2236,9 +2240,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2252,14 +2256,14 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2269,7 +2273,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2279,15 +2283,15 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2298,7 +2302,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2312,7 +2316,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2322,7 +2326,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2335,8 +2339,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2354,13 +2358,13 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2369,9 +2373,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2384,8 +2388,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2395,7 +2399,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2403,16 +2407,16 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2425,16 +2429,16 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2443,7 +2447,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2452,7 +2456,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2461,23 +2465,23 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2492,9 +2496,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2507,8 +2511,8 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "repository_access_level", default: 20, null: false
- t.integer "pages_access_level", default: 20, null: false
- t.index ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
+ t.integer "pages_access_level", null: false
+ 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|
@@ -2518,8 +2522,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2528,7 +2532,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2552,21 +2556,21 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2581,12 +2585,12 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2601,8 +2605,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2610,7 +2614,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2687,31 +2691,31 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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|
@@ -2721,8 +2725,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2733,9 +2737,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2750,10 +2754,10 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2763,9 +2767,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2775,9 +2779,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2785,9 +2789,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2795,7 +2799,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2805,9 +2809,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2815,8 +2819,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2826,9 +2830,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2836,8 +2840,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2849,7 +2853,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2871,8 +2875,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2881,8 +2885,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2891,8 +2895,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2907,9 +2911,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2929,15 +2933,15 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2951,11 +2955,11 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2963,9 +2967,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2975,9 +2979,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2987,7 +2991,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -2995,7 +2999,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3009,7 +3013,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3036,14 +3040,14 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3054,16 +3058,16 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3081,25 +3085,25 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3124,8 +3128,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3138,7 +3142,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3147,7 +3151,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3158,17 +3162,17 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3177,9 +3181,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3190,9 +3194,9 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3208,20 +3212,20 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3233,8 +3237,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3248,10 +3252,10 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3262,14 +3266,14 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3278,15 +3282,15 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3305,7 +3309,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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.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|
@@ -3313,7 +3317,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3322,7 +3326,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3391,7 +3395,7 @@ ActiveRecord::Schema.define(version: 20190703130053) do
t.integer "theme_id", limit: 2
t.integer "accepted_term_id"
t.string "feed_token"
- t.boolean "private_profile"
+ t.boolean "private_profile", default: false
t.boolean "include_private_contributions"
t.string "commit_email"
t.boolean "auditor", default: false, null: false
@@ -3405,26 +3409,26 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3432,8 +3436,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3441,8 +3445,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3459,12 +3463,12 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3476,7 +3480,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3484,8 +3488,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3493,8 +3497,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3512,10 +3516,10 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3524,7 +3528,7 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3540,8 +3544,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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|
@@ -3568,8 +3572,8 @@ ActiveRecord::Schema.define(version: 20190703130053) 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 "application_settings", "namespaces", column: "custom_project_templates_group_id", on_delete: :nullify
diff --git a/doc/README.md b/doc/README.md
index 25db0efb960..bf7017d8fb4 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -108,7 +108,7 @@ The following documentation relates to the DevOps **Plan** stage:
| Plan Topics | Description |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
| [Burndown Charts](user/project/milestones/burndown_charts.md) **(STARTER)** | Watch your project's progress throughout a specific milestone. |
-| [Discussions](user/discussions/index.md) | Threads, comments, and resolvable discussions in issues, commits, and merge requests. |
+| [Discussions](user/discussions/index.md) | Threads, comments, and resolvable threads in issues, commits, and merge requests. |
| [Due Dates](user/project/issues/due_dates.md) | Keep track of issue deadlines. |
| [Epics](user/group/epics/index.md) **(ULTIMATE)** | Tracking groups of issues that share a theme. |
| [Issues](user/project/issues/index.md), including [confidential issues](user/project/issues/confidential_issues.md),<br/>[issue and merge request templates](user/project/description_templates.md),<br/>and [moving issues](user/project/issues/managing_issues.md#moving-issues) | Project issues, restricting access to issues, create templates for submitting new issues and merge requests, and moving issues between projects. |
@@ -192,7 +192,7 @@ The following documentation relates to the DevOps **Create** stage:
|:------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------|
| [Checking out merge requests locally](user/project/merge_requests/index.md#checkout-merge-requests-locally) | Tips for working with merge requests locally. |
| [Cherry-picking](user/project/merge_requests/cherry_pick_changes.md) | Use GitLab for cherry-picking changes. |
-| [Merge request discussion resolution](user/discussions/index.md#moving-a-single-discussion-to-a-new-issue) | Resolve discussions, move discussions in a merge request to an issue, and only allow merge requests to be merged if all discussions are resolved. |
+| [Merge request thread resolution](user/discussions/index.md#moving-a-single-thread-to-a-new-issue) | Resolve threads, move threads in a merge request to an issue, and only allow merge requests to be merged if all threads are resolved. |
| [Merge requests](user/project/merge_requests/index.md) | Merge request management. |
| [Work In Progress "WIP" merge requests](user/project/merge_requests/work_in_progress_merge_requests.md) | Prevent merges of work-in-progress merge requests. |
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index ae1f6e00ea5..aaa43f67760 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -94,12 +94,13 @@ 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
(for authentication events).
-![audit log](audit_log.png)
+![audit log](img/audit_log.png)
### Missing events
diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md
index daba5b4ca82..65d36612d85 100644
--- a/doc/administration/auditor_users.md
+++ b/doc/administration/auditor_users.md
@@ -52,7 +52,7 @@ section.
**Admin Area > Users**. You will find the option of the access level under
the 'Access' section.
- ![Admin Area Form](auditor_access_form.png)
+ ![Admin Area Form](img/auditor_access_form.png)
1. Click **Save changes** or **Create user** for the changes to take effect.
diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md
index d8094587d14..2fc9db0632e 100644
--- a/doc/administration/auth/README.md
+++ b/doc/administration/auth/README.md
@@ -1,19 +1,34 @@
---
comments: false
+type: index
---
-# Authentication and Authorization
+# GitLab authentication and authorization
GitLab integrates with the following external authentication and authorization
-providers.
+providers:
-- [LDAP](ldap.md) Includes Active Directory, Apple Open Directory, Open LDAP,
- and 389 Server
+- [Auth0](../../integration/auth0.md)
+- [Authentiq](authentiq.md)
+- [Azure](../../integration/azure.md)
+- [Bitbucket Cloud](../../integration/bitbucket.md)
+- [CAS](../../integration/cas.md)
+- [Crowd](../../integration/crowd.md)
+- [Facebook](../../integration/facebook.md)
+- [GitHub](../../integration/github.md)
+- [GitLab.com](../../integration/gitlab.md)
+- [Google](../../integration/google.md)
+- [JWT](jwt.md)
+- [Kerberos](../../integration/kerberos.md)
+- [LDAP](ldap.md): Includes Active Directory, Apple Open Directory, Open LDAP,
+ and 389 Server.
- [LDAP for GitLab EE](ldap-ee.md): LDAP additions to GitLab Enterprise Editions **(STARTER ONLY)**
-- [OmniAuth](../../integration/omniauth.md) Sign in via Twitter, GitHub, GitLab.com, Google,
- Bitbucket, Facebook, Shibboleth, Crowd, Azure, Authentiq ID, and JWT
-- [CAS](../../integration/cas.md) Configure GitLab to sign in using CAS
-- [SAML](../../integration/saml.md) Configure GitLab as a SAML 2.0 Service Provider
-- [Okta](okta.md) Configure GitLab to sign in using Okta
-- [Authentiq](authentiq.md): Enable the Authentiq OmniAuth provider for passwordless authentication
-- [Smartcard](smartcard.md) Smartcard authentication **(PREMIUM ONLY)**
+ - [Google Secure LDAP](google_secure_ldap.md)
+- [Okta](okta.md)
+- [Salesforce](../../integration/salesforce.md)
+- [SAML](../../integration/saml.md)
+- [SAML for GitLab.com groups](../../user/group/saml_sso/index.md) **(SILVER ONLY)**
+- [Shibboleth](../../integration/shibboleth.md)
+- [Smartcard](smartcard.md) **(PREMIUM ONLY)**
+- [Twitter](../../integration/twitter.md)
+- [UltraAuth](../../integration/ultra_auth.md)
diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md
index 726622d8599..b84eca4ef0d 100644
--- a/doc/administration/auth/authentiq.md
+++ b/doc/administration/auth/authentiq.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Authentiq OmniAuth Provider
To enable the Authentiq OmniAuth provider for passwordless authentication you must register an application with Authentiq.
@@ -8,47 +12,48 @@ Authentiq will generate a Client ID and the accompanying Client Secret for you t
1. On your GitLab server, open the configuration file:
- For omnibus installation
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ For omnibus installation
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
- ```
+ ```sh
+ sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
+ ```
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings to enable single sign-on and add Authentiq as an OAuth provider.
1. Add the provider configuration for Authentiq:
- For Omnibus packages:
-
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "authentiq",
- "app_id" => "YOUR_CLIENT_ID",
- "app_secret" => "YOUR_CLIENT_SECRET",
- "args" => {
- "scope": 'aq:name email~rs address aq:push'
- }
- }
- ]
- ```
-
- For installations from source:
-
- ```yaml
- - { name: 'authentiq',
- app_id: 'YOUR_CLIENT_ID',
- app_secret: 'YOUR_CLIENT_SECRET',
- args: {
- scope: 'aq:name email~rs address aq:push'
- }
- }
- ```
+ For Omnibus packages:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "authentiq",
+ "app_id" => "YOUR_CLIENT_ID",
+ "app_secret" => "YOUR_CLIENT_SECRET",
+ "args" => {
+ "scope": 'aq:name email~rs address aq:push'
+ }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```yaml
+ - { name: 'authentiq',
+ app_id: 'YOUR_CLIENT_ID',
+ app_secret: 'YOUR_CLIENT_SECRET',
+ args: {
+ scope: 'aq:name email~rs address aq:push'
+ }
+ }
+ ```
1. The `scope` is set to request the user's name, email (required and signed), and permission to send push notifications to sign in on subsequent visits.
See [OmniAuth Authentiq strategy](https://github.com/AuthentiqID/omniauth-authentiq/wiki/Scopes,-callback-url-configuration-and-responses) for more information on scopes and modifiers.
@@ -65,3 +70,15 @@ On the sign in page there should now be an Authentiq icon below the regular sign
- If not they will be prompted to download the app and then follow the procedure above.
If everything goes right, the user will be returned to GitLab and will be signed in.
+
+<!-- ## 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/auth/crowd.md b/doc/administration/auth/crowd.md
index 6db74958d5a..ac63b4f2b97 100644
--- a/doc/administration/auth/crowd.md
+++ b/doc/administration/auth/crowd.md
@@ -1,60 +1,67 @@
+---
+type: reference
+---
+
# Atlassian Crowd OmniAuth Provider
+Authenticate to GitLab using the Atlassian Crowd OmniAuth provider.
+
## Configure a new Crowd application
1. Choose 'Applications' in the top menu, then 'Add application'.
1. Go through the 'Add application' steps, entering the appropriate details.
The screenshot below shows an example configuration.
- ![Example Crowd application configuration](img/crowd_application.png)
+ ![Example Crowd application configuration](img/crowd_application.png)
## Configure GitLab
1. On your GitLab server, open the configuration file.
- **Omnibus:**
+ **Omnibus:**
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- **Source:**
+ **Source:**
- ```sh
- cd /home/git/gitlab
+ ```sh
+ cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ sudo -u git -H editor config/gitlab.yml
+ ```
1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration)
for initial settings.
1. Add the provider configuration:
- **Omnibus:**
-
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "crowd",
- "args" => {
- "crowd_server_url" => "CROWD_SERVER_URL",
- "application_name" => "YOUR_APP_NAME",
- "application_password" => "YOUR_APP_PASSWORD"
- }
- }
- ]
- ```
-
- **Source:**
-
- ```
- - { name: 'crowd',
- args: {
- crowd_server_url: 'CROWD_SERVER_URL',
- application_name: 'YOUR_APP_NAME',
- application_password: 'YOUR_APP_PASSWORD' } }
- ```
+ **Omnibus:**
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "crowd",
+ "args" => {
+ "crowd_server_url" => "CROWD_SERVER_URL",
+ "application_name" => "YOUR_APP_NAME",
+ "application_password" => "YOUR_APP_PASSWORD"
+ }
+ }
+ ]
+ ```
+
+ **Source:**
+
+ ```
+ - { name: 'crowd',
+ args: {
+ crowd_server_url: 'CROWD_SERVER_URL',
+ application_name: 'YOUR_APP_NAME',
+ application_password: 'YOUR_APP_PASSWORD' } }
+ ```
+
1. Change `CROWD_SERVER_URL` to the URL of your Crowd server.
1. Change `YOUR_APP_NAME` to the application name from Crowd applications page.
1. Change `YOUR_APP_PASSWORD` to the application password you've set.
@@ -77,4 +84,4 @@ could not authorize you from Crowd because invalid credentials
Please make sure the Crowd users who need to login to GitLab are authorized to [the application](#configure-a-new-crowd-application) in the step of **Authorisation**. This could be verified by try "Authentication test" for Crowd as of 2.11.
-![Example Crowd application authorisation configuration](img/crowd_application_authorisation.png) \ No newline at end of file
+![Example Crowd application authorisation configuration](img/crowd_application_authorisation.png)
diff --git a/doc/administration/auth/google_secure_ldap.md b/doc/administration/auth/google_secure_ldap.md
index 1db5bb4bc3f..55e6f53622c 100644
--- a/doc/administration/auth/google_secure_ldap.md
+++ b/doc/administration/auth/google_secure_ldap.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Google Secure LDAP **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/46391) in GitLab 11.9.
@@ -66,7 +70,7 @@ values obtained during the LDAP client configuration earlier:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
+ ```ruby
gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
main: # 'main' is the GitLab 'provider ID' of this LDAP server
@@ -127,7 +131,7 @@ values obtained during the LDAP client configuration earlier:
AcZSFJQjdg5BTyvdEDhaYUKGdRw=
-----END PRIVATE KEY-----
EOS
- ```
+ ```
1. Save the file and [reconfigure] GitLab for the changes to take effect.
@@ -137,7 +141,7 @@ values obtained during the LDAP client configuration earlier:
1. Edit `config/gitlab.yml`:
- ```yaml
+ ```yaml
ldap:
enabled: true
servers:
@@ -204,3 +208,15 @@ values obtained during the LDAP client configuration earlier:
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: ../restart_gitlab.md#installations-from-source
+
+<!-- ## 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/auth/how_to_configure_ldap_gitlab_ce/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
index 320a65b665d..86dd398343b 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ce/index.md
@@ -1,15 +1,9 @@
---
-author: Chris Wilson
-author_gitlab: MrChrisW
-level: intermediary
-article_type: admin guide
-date: 2017-05-03
+type: howto
---
# How to configure LDAP with GitLab CE
-## Introduction
-
Managing a large number of users in GitLab can become a burden for system administrators. As an organization grows so do user accounts. Keeping these user accounts in sync across multiple enterprise applications often becomes a time consuming task.
In this guide we will focus on configuring GitLab with Active Directory. [Active Directory](https://en.wikipedia.org/wiki/Active_Directory) is a popular LDAP compatible directory service provided by Microsoft, included in all modern Windows Server operating systems.
@@ -268,3 +262,15 @@ have extended functionalities with LDAP, such as:
- Multiple LDAP servers
Read through the article on [LDAP for GitLab EE](../how_to_configure_ldap_gitlab_ee/index.md) **(STARTER ONLY)** for an overview.
+
+<!-- ## 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/auth/how_to_configure_ldap_gitlab_ee/index.md b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md
index 2683950f143..366acb9ed3e 100644
--- a/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md
+++ b/doc/administration/auth/how_to_configure_ldap_gitlab_ee/index.md
@@ -1,16 +1,10 @@
---
-author: Chris Wilson
-author_gitlab: MrChrisW
-level: intermediary
-article_type: admin guide
-date: 2017-05-03
+type: howto
---
# How to configure LDAP with GitLab EE **(STARTER ONLY)**
-## Introduction
-
-The present article follows [How to Configure LDAP with GitLab CE](../how_to_configure_ldap_gitlab_ce/index.md). Make sure to read through it before moving forward.
+This article expands on [How to Configure LDAP with GitLab CE](../how_to_configure_ldap_gitlab_ce/index.md). Make sure to read through it before moving forward.
## GitLab Enterprise Edition - LDAP features
@@ -117,3 +111,15 @@ Integration of GitLab with Active Directory (LDAP) reduces the complexity of use
It has the advantage of improving user permission controls, whilst easing the deployment of GitLab into an existing [IT environment](https://www.techopedia.com/definition/29199/it-infrastructure). GitLab EE offers advanced group management and multiple LDAP servers.
With the assistance of the [GitLab Support](https://about.gitlab.com/support) team, setting up GitLab with an existing AD/LDAP solution will be a smooth and painless process.
+
+<!-- ## 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/auth/jwt.md b/doc/administration/auth/jwt.md
index 497298503ad..e6b3287ce60 100644
--- a/doc/administration/auth/jwt.md
+++ b/doc/administration/auth/jwt.md
@@ -1,67 +1,71 @@
+---
+type: reference
+---
+
# JWT OmniAuth provider
To enable the JWT OmniAuth provider, you must register your application with JWT.
JWT will provide you with a secret key for you to use.
-1. On your GitLab server, open the configuration file.
-
- For Omnibus GitLab:
-
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
-
- For installations from source:
-
- ```sh
- cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
-
-1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Add the provider configuration.
-
- For Omnibus GitLab:
-
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- { name: 'jwt',
- args: {
- secret: 'YOUR_APP_SECRET',
- algorithm: 'HS256', # Supported algorithms: 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512'
- uid_claim: 'email',
- required_claims: ['name', 'email'],
- info_maps: { name: 'name', email: 'email' },
- auth_url: 'https://example.com/',
- valid_within: 3600 # 1 hour
- }
- }
- ]
- ```
-
- For installation from source:
-
- ```
- - { name: 'jwt',
- args: {
- secret: 'YOUR_APP_SECRET',
- algorithm: 'HS256', # Supported algorithms: 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512'
- uid_claim: 'email',
- required_claims: ['name', 'email'],
- info_map: { name: 'name', email: 'email' },
- auth_url: 'https://example.com/',
- valid_within: 3600 # 1 hour
- }
- }
- ```
-
- NOTE: **Note:** For more information on each configuration option refer to
- the [OmniAuth JWT usage documentation](https://github.com/mbleigh/omniauth-jwt#usage).
-
-1. Change `YOUR_APP_SECRET` to the client secret and set `auth_url` to your redirect URL.
-1. Save the configuration file.
-1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. On your GitLab server, open the configuration file.
+
+ For Omnibus GitLab:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
+1. Add the provider configuration.
+
+ For Omnibus GitLab:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ { name: 'jwt',
+ args: {
+ secret: 'YOUR_APP_SECRET',
+ algorithm: 'HS256', # Supported algorithms: 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512'
+ uid_claim: 'email',
+ required_claims: ['name', 'email'],
+ info_maps: { name: 'name', email: 'email' },
+ auth_url: 'https://example.com/',
+ valid_within: 3600 # 1 hour
+ }
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```
+ - { name: 'jwt',
+ args: {
+ secret: 'YOUR_APP_SECRET',
+ algorithm: 'HS256', # Supported algorithms: 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512'
+ uid_claim: 'email',
+ required_claims: ['name', 'email'],
+ info_map: { name: 'name', email: 'email' },
+ auth_url: 'https://example.com/',
+ valid_within: 3600 # 1 hour
+ }
+ }
+ ```
+
+ NOTE: **Note:** For more information on each configuration option refer to
+ the [OmniAuth JWT usage documentation](https://github.com/mbleigh/omniauth-jwt#usage).
+
+1. Change `YOUR_APP_SECRET` to the client secret and set `auth_url` to your redirect URL.
+1. Save the configuration file.
+1. [Reconfigure][] or [restart GitLab][] 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 a JWT icon below the regular sign in form.
Click the icon to begin the authentication process. JWT will ask the user to
@@ -70,3 +74,15 @@ will be redirected to GitLab and will be signed in.
[reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../restart_gitlab.md#installations-from-source
+
+<!-- ## 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/auth/ldap-ee.md b/doc/administration/auth/ldap-ee.md
index 1a8af0827ee..1b7af2d945a 100644
--- a/doc/administration/auth/ldap-ee.md
+++ b/doc/administration/auth/ldap-ee.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# LDAP Additions in GitLab EE **(STARTER ONLY)**
This is a continuation of the main [LDAP documentation](ldap.md), detailing LDAP
@@ -85,19 +89,19 @@ following.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['ldap_servers'] = YAML.load <<-EOS
- main:
- ## snip...
- ##
- ## Base where we can search for groups
- ##
- ## Ex. ou=groups,dc=gitlab,dc=example
- ##
- ##
- group_base: ou=groups,dc=example,dc=com
- EOS
- ```
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ ## snip...
+ ##
+ ## Base where we can search for groups
+ ##
+ ## Ex. ou=groups,dc=gitlab,dc=example
+ ##
+ ##
+ group_base: ou=groups,dc=example,dc=com
+ EOS
+ ```
1. [Reconfigure GitLab][reconfigure] for the changes to take effect.
@@ -105,14 +109,14 @@ following.
1. Edit `/home/git/gitlab/config/gitlab.yml`:
- ```yaml
- production:
- ldap:
- servers:
- main:
- # snip...
- group_base: ou=groups,dc=example,dc=com
- ```
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ group_base: ou=groups,dc=example,dc=com
+ ```
1. [Restart GitLab][restart] for the changes to take effect.
@@ -140,30 +144,30 @@ group, as opposed to the full DN.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['ldap_servers'] = YAML.load <<-EOS
- main:
- ## snip...
- ##
- ## Base where we can search for groups
- ##
- ## Ex. ou=groups,dc=gitlab,dc=example
- ##
- ##
- group_base: ou=groups,dc=example,dc=com
-
- ##
- ## The CN of a group containing GitLab administrators
- ##
- ## Ex. administrators
- ##
- ## Note: Not `cn=administrators` or the full DN
- ##
- ##
- admin_group: my_admin_group
-
- EOS
- ```
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ ## snip...
+ ##
+ ## Base where we can search for groups
+ ##
+ ## Ex. ou=groups,dc=gitlab,dc=example
+ ##
+ ##
+ group_base: ou=groups,dc=example,dc=com
+
+ ##
+ ## The CN of a group containing GitLab administrators
+ ##
+ ## Ex. administrators
+ ##
+ ## Note: Not `cn=administrators` or the full DN
+ ##
+ ##
+ admin_group: my_admin_group
+
+ EOS
+ ```
1. [Reconfigure GitLab][reconfigure] for the changes to take effect.
@@ -171,15 +175,15 @@ group, as opposed to the full DN.
1. Edit `/home/git/gitlab/config/gitlab.yml`:
- ```yaml
- production:
- ldap:
- servers:
- main:
- # snip...
- group_base: ou=groups,dc=example,dc=com
- admin_group: my_admin_group
- ```
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ group_base: ou=groups,dc=example,dc=com
+ admin_group: my_admin_group
+ ```
1. [Restart GitLab][restart] for the changes to take effect.
@@ -189,8 +193,7 @@ group, as opposed to the full DN.
to lock down user abilities to invite new members to a group. When enabled following happens:
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.
-
+1. 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
@@ -211,9 +214,9 @@ sync to run once every 12 hours at the top of the hour.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['ldap_sync_worker_cron'] = "0 */12 * * *"
- ```
+ ```ruby
+ gitlab_rails['ldap_sync_worker_cron'] = "0 */12 * * *"
+ ```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
@@ -221,11 +224,11 @@ sync to run once every 12 hours at the top of the hour.
1. Edit `config/gitlab.yaml`:
- ```yaml
- cron_jobs:
- ldap_sync_worker_cron:
- "0 */12 * * *"
- ```
+ ```yaml
+ cron_jobs:
+ ldap_sync_worker_cron:
+ "0 */12 * * *"
+ ```
1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
@@ -252,9 +255,9 @@ sync to run once every 2 hours at the top of the hour.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['ldap_group_sync_worker_cron'] = "0 */2 * * * *"
- ```
+ ```ruby
+ gitlab_rails['ldap_group_sync_worker_cron'] = "0 */2 * * * *"
+ ```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
@@ -262,11 +265,11 @@ sync to run once every 2 hours at the top of the hour.
1. Edit `config/gitlab.yaml`:
- ```yaml
- cron_jobs:
- ldap_group_sync_worker_cron:
- "*/30 * * * *"
- ```
+ ```yaml
+ cron_jobs:
+ ldap_group_sync_worker_cron:
+ "*/30 * * * *"
+ ```
1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
@@ -283,20 +286,20 @@ task.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['ldap_servers'] = YAML.load <<-EOS
- main:
- ## snip...
- ##
- ## An array of CNs of groups containing users that should be considered external
- ##
- ## Ex. ['interns', 'contractors']
- ##
- ## Note: Not `cn=interns` or the full DN
- ##
- external_groups: ['interns', 'contractors']
- EOS
- ```
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ ## snip...
+ ##
+ ## An array of CNs of groups containing users that should be considered external
+ ##
+ ## Ex. ['interns', 'contractors']
+ ##
+ ## Note: Not `cn=interns` or the full DN
+ ##
+ external_groups: ['interns', 'contractors']
+ EOS
+ ```
1. [Reconfigure GitLab][reconfigure] for the changes to take effect.
@@ -304,14 +307,14 @@ task.
1. Edit `config/gitlab.yaml`:
- ```yaml
- production:
- ldap:
- servers:
- main:
- # snip...
- external_groups: ['interns', 'contractors']
- ```
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ external_groups: ['interns', 'contractors']
+ ```
1. [Restart GitLab][restart] for the changes to take effect.
@@ -436,66 +439,71 @@ step of the sync.
1. Start a Rails console
- ```bash
- # For Omnibus installations
- sudo gitlab-rails console
+ ```bash
+ # For Omnibus installations
+ sudo gitlab-rails console
- # For installations from source
- sudo -u git -H bundle exec rails console production
- ```
+ # 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
- Rails.logger.level = Logger::DEBUG
- ```
+ ```ruby
+ Rails.logger.level = Logger::DEBUG
+ ```
+
1. Choose a GitLab group to test with. This group should have an LDAP group link
already configured. If the output is `nil`, the group could not be found.
If a bunch of group attributes are output, your group was found successfully.
- ```ruby
- group = Group.find_by(name: 'my_group')
+ ```ruby
+ group = Group.find_by(name: 'my_group')
+
+ # Output
+ => #<Group:0x007fe825196558 id: 1234, name: "my_group"...>
+ ```
- # Output
- => #<Group:0x007fe825196558 id: 1234, name: "my_group"...>
- ```
1. Run a group sync for this particular group.
- ```ruby
- EE::Gitlab::Auth::LDAP::Sync::Group.execute_all_providers(group)
- ```
+ ```ruby
+ EE::Gitlab::Auth::LDAP::Sync::Group.execute_all_providers(group)
+ ```
+
1. Look through the output of the sync. See [example log output](#example-log-output)
below for more information about the output.
1. If you still aren't able to see why the user isn't being added, query the
LDAP group directly to see what members are listed. Still in the Rails console,
run the following query:
- ```ruby
- adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
- ldap_group = EE::Gitlab::Auth::LDAP::Group.find_by_cn('group_cn_here', adapter)
+ ```ruby
+ adapter = Gitlab::Auth::LDAP::Adapter.new('ldapmain') # If `main` is the LDAP provider
+ ldap_group = EE::Gitlab::Auth::LDAP::Group.find_by_cn('group_cn_here', adapter)
+
+ # Output
+ => #<EE::Gitlab::Auth::LDAP::Group:0x007fcbdd0bb6d8
+ ```
- # Output
- => #<EE::Gitlab::Auth::LDAP::Group:0x007fcbdd0bb6d8
- ```
1. Query the LDAP group's member DNs and see if the user's DN is in the list.
One of the DNs here should match the 'Identifier' from the LDAP identity
checked earlier. If it doesn't, the user does not appear to be in the LDAP
group.
- ```ruby
- ldap_group.member_dns
+ ```ruby
+ ldap_group.member_dns
+
+ # Output
+ => ["uid=john,ou=people,dc=example,dc=com", "uid=mary,ou=people,dc=example,dc=com"]
+ ```
- # Output
- => ["uid=john,ou=people,dc=example,dc=com", "uid=mary,ou=people,dc=example,dc=com"]
- ```
1. Some LDAP servers don't store members by DN. Rather, they use UIDs instead.
If you didn't see results from the last query, try querying by UIDs instead.
- ```ruby
- ldap_group.member_uids
+ ```ruby
+ ldap_group.member_uids
- # Output
- => ['john','mary']
- ```
+ # Output
+ => ['john','mary']
+ ```
#### Example log output
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index 2144f5753a8..be05a4d63a7 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
<!-- If the change is EE-specific, put it in `ldap-ee.md`, NOT here. -->
# LDAP
@@ -398,30 +402,30 @@ The `user_filter` DN can contain special characters. For example:
- A comma:
- ```
- OU=GitLab, Inc,DC=gitlab,DC=com
- ```
+ ```
+ OU=GitLab, Inc,DC=gitlab,DC=com
+ ```
- Open and close brackets:
- ```
- OU=Gitlab (Inc),DC=gitlab,DC=com
- ```
+ ```
+ OU=Gitlab (Inc),DC=gitlab,DC=com
+ ```
- These characters must be escaped as documented in
- [RFC 4515](https://tools.ietf.org/search/rfc4515).
+ These characters must be escaped as documented in
+ [RFC 4515](https://tools.ietf.org/search/rfc4515).
- Escape commas with `\2C`. For example:
- ```
- OU=GitLab\2C Inc,DC=gitlab,DC=com
- ```
+ ```
+ OU=GitLab\2C Inc,DC=gitlab,DC=com
+ ```
- Escape open and close brackets with `\28` and `\29`, respectively. For example:
- ```
- OU=Gitlab \28Inc\29,DC=gitlab,DC=com
- ```
+ ```
+ OU=Gitlab \28Inc\29,DC=gitlab,DC=com
+ ```
## Enabling LDAP sign-in for existing GitLab users
@@ -445,13 +449,13 @@ the configuration option `lowercase_usernames`. By default, this configuration o
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['ldap_servers'] = YAML.load <<-EOS
- main:
- # snip...
- lowercase_usernames: true
- EOS
- ```
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ # snip...
+ lowercase_usernames: true
+ EOS
+ ```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
@@ -459,14 +463,14 @@ the configuration option `lowercase_usernames`. By default, this configuration o
1. Edit `config/gitlab.yaml`:
- ```yaml
- production:
- ldap:
- servers:
- main:
- # snip...
- lowercase_usernames: true
- ```
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ lowercase_usernames: true
+ ```
1. [Restart GitLab](../restart_gitlab.md#installations-from-source) for the changes to take effect.
@@ -494,6 +498,13 @@ be mandatory and clients cannot be authenticated with the TLS protocol.
## Troubleshooting
+If a user account is blocked or unblocked due to the LDAP configuration, a
+message will be logged to `application.log`.
+
+If there is an unexpected error during an LDAP lookup (configuration error,
+timeout), the login is rejected and a message will be logged to
+`production.log`.
+
### Debug LDAP user filter with ldapsearch
This example uses ldapsearch and assumes you are using ActiveDirectory. The
@@ -519,26 +530,17 @@ ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt -b "$ba
- Run the following check command to make sure that the LDAP settings are
correct and GitLab can see your users:
- ```bash
- # For Omnibus installations
- sudo gitlab-rake gitlab:ldap:check
+ ```bash
+ # For Omnibus installations
+ sudo gitlab-rake gitlab:ldap:check
- # For installations from source
- sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
- ```
+ # For installations from source
+ sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production
+ ```
-### Connection Refused
+### Connection refused
If you are getting 'Connection Refused' errors when trying to connect to the
LDAP server please double-check the LDAP `port` and `encryption` settings used by
GitLab. Common combinations are `encryption: 'plain'` and `port: 389`, OR
`encryption: 'simple_tls'` and `port: 636`.
-
-### Troubleshooting
-
-If a user account is blocked or unblocked due to the LDAP configuration, a
-message will be logged to `application.log`.
-
-If there is an unexpected error during an LDAP lookup (configuration error,
-timeout), the login is rejected and a message will be logged to
-`production.log`.
diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md
index 6e48add6930..78d040cda99 100644
--- a/doc/administration/auth/oidc.md
+++ b/doc/administration/auth/oidc.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# OpenID Connect OmniAuth provider
GitLab can use [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) as an OmniAuth provider.
@@ -5,82 +9,89 @@ GitLab can use [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0
To enable the OpenID Connect OmniAuth provider, you must register your application with an OpenID Connect provider.
The OpenID Connect will provide you with a client details and secret for you to use.
-1. On your GitLab server, open the configuration file.
-
- For Omnibus GitLab:
-
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
-
- For installations from source:
-
- ```sh
- cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
-
- See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
-
-1. Add the provider configuration.
-
- For Omnibus GitLab:
-
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- { 'name' => 'openid_connect',
- 'label' => '<your_oidc_label>',
- 'args' => {
- "name' => 'openid_connect',
- 'scope' => ['openid','profile'],
- 'response_type' => 'code',
- 'issuer' => '<your_oidc_url>',
- 'discovery' => true,
- 'client_auth_method' => 'query',
- 'uid_field' => '<uid_field>',
- 'client_options' => {
- 'identifier' => '<your_oidc_client_id>',
- 'secret' => '<your_oidc_client_secret>',
- 'redirect_uri' => '<your_gitlab_url>/users/auth/openid_connect/callback'
- }
- }
- }
- ]
- ```
-
- For installation from source:
-
- ```yaml
- - { name: 'openid_connect',
- label: '<your_oidc_label>',
- args: {
- name: 'openid_connect',
- scope: ['openid','profile'],
- response_type: 'code',
- issuer: '<your_oidc_url>',
- discovery: true,
- client_auth_method: 'query',
- uid_field: '<uid_field>',
- client_options: {
- identifier: '<your_oidc_client_id>',
- secret: '<your_oidc_client_secret>',
- redirect_uri: '<your_gitlab_url>/users/auth/openid_connect/callback'
- }
- }
- }
- ```
-
- > **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. On your GitLab server, open the configuration file.
+
+ For Omnibus GitLab:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+ See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration.
+
+ For Omnibus GitLab:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ { 'name' => 'openid_connect',
+ 'label' => '<your_oidc_label>',
+ 'args' => {
+ "name' => 'openid_connect',
+ 'scope' => ['openid','profile'],
+ 'response_type' => 'code',
+ 'issuer' => '<your_oidc_url>',
+ 'discovery' => true,
+ 'client_auth_method' => 'query',
+ 'uid_field' => '<uid_field>',
+ 'client_options' => {
+ 'identifier' => '<your_oidc_client_id>',
+ 'secret' => '<your_oidc_client_secret>',
+ 'redirect_uri' => '<your_gitlab_url>/users/auth/openid_connect/callback'
+ }
+ }
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```yaml
+ - { name: 'openid_connect',
+ label: '<your_oidc_label>',
+ args: {
+ name: 'openid_connect',
+ scope: ['openid','profile'],
+ response_type: 'code',
+ issuer: '<your_oidc_url>',
+ discovery: true,
+ client_auth_method: 'query',
+ uid_field: '<uid_field>',
+ client_options: {
+ identifier: '<your_oidc_client_id>',
+ secret: '<your_oidc_client_secret>',
+ redirect_uri: '<your_gitlab_url>/users/auth/openid_connect/callback'
+ }
+ }
+ }
+ ```
+
+ > **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:
@@ -139,7 +150,7 @@ for more details:
}
```
-### Troubleshooting
+## Troubleshooting
If you're having trouble, here are some tips:
@@ -155,9 +166,9 @@ If you're having trouble, here are some tips:
`https://accounts.google.com/.well-known/openid-configuration`.
1. The OpenID Connect client uses HTTP Basic Authentication to send the
- OAuth2 access token. For example, if you are seeing 401 errors upon
- retrieving the `userinfo` endpoint, you may want to check your OpenID
- Web server configuration. For example, for
+ OAuth2 access token if `client_auth_method` is not defined or if set to `basic`.
+ If you are seeing 401 errors upon retrieving the `userinfo` endpoint, you may
+ want to check your OpenID Web server configuration. For example, for
[oauth2-server-php](https://github.com/bshaffer/oauth2-server-php), you
may need to [add a configuration parameter to
Apache](https://github.com/bshaffer/oauth2-server-php/issues/926#issuecomment-387502778).
diff --git a/doc/administration/auth/okta.md b/doc/administration/auth/okta.md
index aa4e1b0d2e0..5524c3ba092 100644
--- a/doc/administration/auth/okta.md
+++ b/doc/administration/auth/okta.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Okta SSO provider
Okta is a [Single Sign-on provider](https://www.okta.com/products/single-sign-on/) that can be used to authenticate
@@ -16,7 +20,7 @@ The following documentation enables Okta as a SAML provider.
1. Next, you'll need the to fill in the SAML general config. Here's an example
image.
- ![Okta admin panel view](img/okta_admin_panel.png)
+ ![Okta admin panel view](img/okta_admin_panel.png)
1. The last part of the configuration is the feedback section where you can
just say you're a customer and creating an app for internal use.
@@ -24,7 +28,7 @@ The following documentation enables Okta as a SAML provider.
profile. Click on the SAML 2.0 config instructions button which should
look like the following:
- ![Okta SAML settings](img/okta_saml_settings.png)
+ ![Okta SAML settings](img/okta_saml_settings.png)
1. On the screen that comes up take note of the
**Identity Provider Single Sign-On URL** which you'll use for the
@@ -38,112 +42,112 @@ Now that the Okta app is configured, it's time to enable it in GitLab.
## Configure GitLab
-1. On your GitLab server, open the configuration file:
-
- **For Omnibus GitLab installations**
-
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
-
- **For installations from source**
-
- ```sh
- cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
-
-1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration)
- for initial settings.
-
-1. To allow your users to use Okta to sign up without having to manually create
- an account first, don't forget to add the following values to your
- configuration:
-
- **For Omnibus GitLab installations**
-
- ```ruby
- gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
- gitlab_rails['omniauth_block_auto_created_users'] = false
- ```
-
- **For installations from source**
-
- ```yaml
- allow_single_sign_on: ["saml"]
- block_auto_created_users: false
- ```
-
-1. You can also automatically link Okta users with existing GitLab users if
- their email addresses match by adding the following setting:
-
- **For Omnibus GitLab installations**
-
- ```ruby
- gitlab_rails['omniauth_auto_link_saml_user'] = true
- ```
-
- **For installations from source**
-
- ```yaml
- auto_link_saml_user: true
- ```
-
-1. Add the provider configuration.
-
- >**Notes:**
- >
- >- Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
- of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
- installation to generate the correct value).
- >
- >- To get the `idp_cert_fingerprint` fingerprint, first download the
- certificate from the Okta app you registered and then run:
- `openssl x509 -in okta.cert -noout -fingerprint`. Substitute `okta.cert`
- with the location of your certificate.
- >
- >- Change the value of `idp_sso_target_url`, with the value of the
- **Identity Provider Single Sign-On URL** from the step when you
- configured the Okta app.
- >
- >- Change the value of `issuer` to the value of the **Audience Restriction** from your Okta app configuration. This will identify GitLab
- to the IdP.
- >
- >- Leave `name_identifier_format` as-is.
-
- **For Omnibus GitLab installations**
-
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- name: 'saml',
- args: {
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
- idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
- idp_sso_target_url: 'https://gitlab.oktapreview.com/app/gitlabdev773716_gitlabsaml_1/exk8odl81tBrjpD4B0h7/sso/saml',
- issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
- },
- label: 'Okta' # optional label for SAML login button, defaults to "Saml"
- }
- ]
- ```
-
- **For installations from source**
-
- ```yaml
- - {
- name: 'saml',
- args: {
- assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
- idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
- idp_sso_target_url: 'https://gitlab.oktapreview.com/app/gitlabdev773716_gitlabsaml_1/exk8odl81tBrjpD4B0h7/sso/saml',
- issuer: 'https://gitlab.example.com',
- name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
- },
- label: 'Okta' # optional label for SAML login button, defaults to "Saml"
- }
- ```
+1. On your GitLab server, open the configuration file:
+
+ **For Omnibus GitLab installations**
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ **For installations from source**
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration)
+ for initial settings.
+
+1. To allow your users to use Okta to sign up without having to manually create
+ an account first, don't forget to add the following values to your
+ configuration:
+
+ **For Omnibus GitLab installations**
+
+ ```ruby
+ gitlab_rails['omniauth_allow_single_sign_on'] = ['saml']
+ gitlab_rails['omniauth_block_auto_created_users'] = false
+ ```
+
+ **For installations from source**
+
+ ```yaml
+ allow_single_sign_on: ["saml"]
+ block_auto_created_users: false
+ ```
+
+1. You can also automatically link Okta users with existing GitLab users if
+ their email addresses match by adding the following setting:
+
+ **For Omnibus GitLab installations**
+
+ ```ruby
+ gitlab_rails['omniauth_auto_link_saml_user'] = true
+ ```
+
+ **For installations from source**
+
+ ```yaml
+ auto_link_saml_user: true
+ ```
+
+1. Add the provider configuration.
+
+ >**Notes:**
+ >
+ >- Change the value for `assertion_consumer_service_url` to match the HTTPS endpoint
+ of GitLab (append `users/auth/saml/callback` to the HTTPS URL of your GitLab
+ installation to generate the correct value).
+ >
+ >- To get the `idp_cert_fingerprint` fingerprint, first download the
+ certificate from the Okta app you registered and then run:
+ `openssl x509 -in okta.cert -noout -fingerprint`. Substitute `okta.cert`
+ with the location of your certificate.
+ >
+ >- Change the value of `idp_sso_target_url`, with the value of the
+ **Identity Provider Single Sign-On URL** from the step when you
+ configured the Okta app.
+ >
+ >- Change the value of `issuer` to the value of the **Audience Restriction** from your Okta app configuration. This will identify GitLab
+ to the IdP.
+ >
+ >- Leave `name_identifier_format` as-is.
+
+ **For Omnibus GitLab installations**
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://gitlab.oktapreview.com/app/gitlabdev773716_gitlabsaml_1/exk8odl81tBrjpD4B0h7/sso/saml',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ },
+ label: 'Okta' # optional label for SAML login button, defaults to "Saml"
+ }
+ ]
+ ```
+
+ **For installations from source**
+
+ ```yaml
+ - {
+ name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://gitlab.oktapreview.com/app/gitlabdev773716_gitlabsaml_1/exk8odl81tBrjpD4B0h7/sso/saml',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ },
+ label: 'Okta' # optional label for SAML login button, defaults to "Saml"
+ }
+ ```
1. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart](../restart_gitlab.md#installations-from-source) GitLab for Omnibus and installations
from source respectively for the changes to take effect.
@@ -157,3 +161,15 @@ Make sure the groups exist and are assigned to the Okta app.
You can take a look of the [SAML documentation](../../integration/saml.md#marking-users-as-external-based-on-saml-groups) on external groups since
it works the same.
+
+<!-- ## 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/auth/smartcard.md b/doc/administration/auth/smartcard.md
index a0d4e9ef3b5..4f236d1afb8 100644
--- a/doc/administration/auth/smartcard.md
+++ b/doc/administration/auth/smartcard.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Smartcard authentication **(PREMIUM ONLY)**
GitLab supports authentication using smartcards.
@@ -22,7 +26,7 @@ To use a smartcard with an X.509 certificate to authenticate against a local
database with GitLab, `CN` and `emailAddress` must be defined in the
certificate. For example:
-```
+```text
Certificate:
Data:
Version: 1 (0x0)
@@ -56,11 +60,11 @@ attribute. As a prerequisite, you must use an LDAP server that:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['smartcard_enabled'] = true
- gitlab_rails['smartcard_ca_file'] = "/etc/ssl/certs/CA.pem"
- gitlab_rails['smartcard_client_certificate_required_port'] = 3444
- ```
+ ```ruby
+ gitlab_rails['smartcard_enabled'] = true
+ gitlab_rails['smartcard_ca_file'] = "/etc/ssl/certs/CA.pem"
+ gitlab_rails['smartcard_client_certificate_required_port'] = 3444
+ ```
1. Save the file and [reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure)
GitLab for the changes to take effect.
@@ -154,15 +158,15 @@ attribute. As a prerequisite, you must use an LDAP server that:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['ldap_servers'] = YAML.load <<-EOS
- main:
- # snip...
- # Enable smartcard authentication against the LDAP server. Valid values
- # are "false", "optional", and "required".
- smartcard_auth: optional
- EOS
- ```
+ ```ruby
+ gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+ main:
+ # snip...
+ # Enable smartcard authentication against the LDAP server. Valid values
+ # are "false", "optional", and "required".
+ smartcard_auth: optional
+ EOS
+ ```
1. Save the file and [reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure)
GitLab for the changes to take effect.
@@ -171,16 +175,16 @@ attribute. As a prerequisite, you must use an LDAP server that:
1. Edit `config/gitlab.yml`:
- ```yaml
- production:
- ldap:
- servers:
- main:
- # snip...
- # Enable smartcard authentication against the LDAP server. Valid values
- # are "false", "optional", and "required".
- smartcard_auth: optional
- ```
+ ```yaml
+ production:
+ ldap:
+ servers:
+ main:
+ # snip...
+ # Enable smartcard authentication against the LDAP server. Valid values
+ # are "false", "optional", and "required".
+ smartcard_auth: optional
+ ```
1. Save the file and [restart](../restart_gitlab.md#installations-from-source)
GitLab for the changes to take effect.
@@ -191,9 +195,9 @@ attribute. As a prerequisite, you must use an LDAP server that:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['smartcard_required_for_git_access'] = true
- ```
+ ```ruby
+ gitlab_rails['smartcard_required_for_git_access'] = true
+ ```
1. Save the file and [reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure)
GitLab for the changes to take effect.
@@ -202,13 +206,25 @@ attribute. As a prerequisite, you must use an LDAP server that:
1. Edit `config/gitlab.yml`:
- ```yaml
- ## Smartcard authentication settings
- smartcard:
- # snip...
- # Browser session with smartcard sign-in is required for Git access
- required_for_git_access: true
- ```
+ ```yaml
+ ## Smartcard authentication settings
+ smartcard:
+ # snip...
+ # Browser session with smartcard sign-in is required for Git access
+ required_for_git_access: true
+ ```
1. Save the file and [restart](../restart_gitlab.md#installations-from-source)
GitLab for the changes to take effect.
+
+<!-- ## 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/container_registry.md b/doc/administration/container_registry.md
index 04f52783d22..3e3054af509 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -696,12 +696,11 @@ project or branch name. Special characters can include:
- Trailing hyphen/dash
- Double hyphen/dash
-To get around this, you can [change the group path](../user/group/index.md#changing-a-groups-path),
-[change the project path](../user/project/settings/index.md#renaming-a-repository) or change the
-branch name. Another option is to create a [push rule](../push_rules/push_rules.html) to prevent
+To get around this, you can [change the group path](../user/group/index.md#changing-a-groups-path),
+[change the project path](../user/project/settings/index.md#renaming-a-repository) or change the
+branch name. Another option is to create a [push rule](../push_rules/push_rules.html) to prevent
this at the instance level.
-
[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
diff --git a/doc/administration/dependency_proxy.md b/doc/administration/dependency_proxy.md
index 776c60703fc..d2c52b67e67 100644
--- a/doc/administration/dependency_proxy.md
+++ b/doc/administration/dependency_proxy.md
@@ -70,6 +70,7 @@ To change the local storage path:
enabled: true
storage_path: shared/dependency_proxy
```
+
1. [Restart GitLab] for the changes to take effect.
### Using object storage
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/background_verification.md b/doc/administration/geo/disaster_recovery/background_verification.md
index 8eee9427b56..27866b7536e 100644
--- a/doc/administration/geo/disaster_recovery/background_verification.md
+++ b/doc/administration/geo/disaster_recovery/background_verification.md
@@ -171,14 +171,21 @@ If the **primary** and **secondary** nodes have a checksum verification mismatch
## Current limitations
-Until [issue #5064][ee-5064] is completed, background verification doesn't cover
-CI job artifacts and traces, LFS objects, or user uploads in file storage.
-Verify their integrity manually by following [these instructions][foreground-verification]
-on both nodes, and comparing the output between them.
+Automatic background verification doesn't cover attachments, LFS objects,
+job artifacts, and user uploads in file storage. You can keep track of the
+progress to include them in [ee-1430]. For now, you can verify their integrity
+manually by following [these instructions][foreground-verification] on both
+nodes, and comparing the output between them.
+
+In GitLab EE 12.1, Geo calculates checksums for attachments, LFS objects and
+archived traces on secondary nodes after the transfer, compares it with the
+stored checksums, and rejects transfers if mismatched. Please note that Geo
+currently does not support an automatic way to verify these data if they have
+been synced before GitLab EE 12.1.
Data in object storage is **not verified**, as the object store is responsible
for ensuring the integrity of the data.
[reset-verification]: background_verification.md#reset-verification-for-projects-where-verification-has-failed
[foreground-verification]: ../../raketasks/check.md
-[ee-5064]: https://gitlab.com/gitlab-org/gitlab-ee/issues/5064
+[ee-1430]: https://gitlab.com/groups/gitlab-org/-/epics/1430
diff --git a/doc/administration/geo/disaster_recovery/bring_primary_back.md b/doc/administration/geo/disaster_recovery/bring_primary_back.md
index 30d35df25c7..64d7ef2d609 100644
--- a/doc/administration/geo/disaster_recovery/bring_primary_back.md
+++ b/doc/administration/geo/disaster_recovery/bring_primary_back.md
@@ -30,7 +30,7 @@ To bring the former **primary** node up to date:
`sudo systemctl enable gitlab-runsvdir`. For CentOS 6, you need to install
the GitLab instance from scratch and set it up as a **secondary** node by
following [Setup instructions][setup-geo]. In this case, you don't need to follow the next step.
-
+
NOTE: **Note:** If you [changed the DNS records](index.md#step-4-optional-updating-the-primary-domain-dns-record)
for this node during disaster recovery procedure you may need to [block
all the writes to this node](planned_failover.md#prevent-updates-to-the-primary-node)
diff --git a/doc/administration/geo/disaster_recovery/planned_failover.md b/doc/administration/geo/disaster_recovery/planned_failover.md
index 393326c3347..0c2d48854f0 100644
--- a/doc/administration/geo/disaster_recovery/planned_failover.md
+++ b/doc/administration/geo/disaster_recovery/planned_failover.md
@@ -187,7 +187,7 @@ access to the **primary** node during the maintenance window.
before it is completed will cause the work to be lost.
1. On the **primary** node, navigate to **Admin Area > Geo** and wait for the
following conditions to be true of the **secondary** node you are failing over to:
-
+
- All replication meters to each 100% replicated, 0% failures.
- All verification meters reach 100% verified, 0% failures.
- Database replication lag is 0ms.
diff --git a/doc/administration/geo/replication/database.md b/doc/administration/geo/replication/database.md
index 71a1ce87833..9272287a4a7 100644
--- a/doc/administration/geo/replication/database.md
+++ b/doc/administration/geo/replication/database.md
@@ -116,7 +116,7 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o
by default. However, Geo requires the **secondary** node to be able to
connect to the **primary** node's database. For this reason, we need the address of
each node.
-
+
NOTE: **Note:** For external PostgreSQL instances, see [additional instructions](external_database.md).
If you are using a cloud provider, you can lookup the addresses for each
@@ -424,22 +424,22 @@ data before running `pg_basebackup`.
- If PostgreSQL is listening on a non-standard port, add `--port=` as well.
- If your database is too large to be transferred in 30 minutes, you will need
- to increase the timeout, e.g., `--backup-timeout=3600` if you expect the
- initial replication to take under an hour.
- - Pass `--sslmode=disable` to skip PostgreSQL TLS authentication altogether
- (e.g., you know the network path is secure, or you are using a site-to-site
- VPN). This is **not** safe over the public Internet!
- - You can read more details about each `sslmode` in the
- [PostgreSQL documentation][pg-docs-ssl];
- the instructions above are carefully written to ensure protection against
- both passive eavesdroppers and active "man-in-the-middle" attackers.
- - Change the `--slot-name` to the name of the replication slot
- to be used on the **primary** database. The script will attempt to create the
- replication slot automatically if it does not exist.
- - If you're repurposing an old server into a Geo **secondary** node, you'll need to
- add `--force` to the command line.
- - When not in a production machine you can disable backup step if you
- really sure this is what you want by adding `--skip-backup`
+ to increase the timeout, e.g., `--backup-timeout=3600` if you expect the
+ initial replication to take under an hour.
+ - Pass `--sslmode=disable` to skip PostgreSQL TLS authentication altogether
+ (e.g., you know the network path is secure, or you are using a site-to-site
+ VPN). This is **not** safe over the public Internet!
+ - You can read more details about each `sslmode` in the
+ [PostgreSQL documentation][pg-docs-ssl];
+ the instructions above are carefully written to ensure protection against
+ both passive eavesdroppers and active "man-in-the-middle" attackers.
+ - Change the `--slot-name` to the name of the replication slot
+ to be used on the **primary** database. The script will attempt to create the
+ replication slot automatically if it does not exist.
+ - If you're repurposing an old server into a Geo **secondary** node, you'll need to
+ add `--force` to the command line.
+ - When not in a production machine you can disable backup step if you
+ really sure this is what you want by adding `--skip-backup`
The replication process is now complete.
diff --git a/doc/administration/geo/replication/external_database.md b/doc/administration/geo/replication/external_database.md
index 85687d4a648..256195998a7 100644
--- a/doc/administration/geo/replication/external_database.md
+++ b/doc/administration/geo/replication/external_database.md
@@ -25,7 +25,6 @@ developed and tested. We aim to be compatible with most external
This command will use your defined `external_url` in `/etc/gitlab/gitlab.rb`.
-
### Configure the external database to be replicated
To set up an external database, you can either:
diff --git a/doc/administration/geo/replication/remove_geo_node.md b/doc/administration/geo/replication/remove_geo_node.md
index 4f64e21f8ef..e24eb2bd428 100644
--- a/doc/administration/geo/replication/remove_geo_node.md
+++ b/doc/administration/geo/replication/remove_geo_node.md
@@ -19,10 +19,10 @@ Once removed from the Geo admin page, you must stop and uninstall the **secondar
```bash
# Stop gitlab and remove its supervision process
sudo gitlab-ctl uninstall
-
+
# Debian/Ubuntu
sudo dpkg --remove gitlab-ee
-
+
# Redhat/Centos
sudo rpm --erase gitlab-ee
```
@@ -32,9 +32,9 @@ Once GitLab has been uninstalled from the **secondary** node, the replication sl
1. On the **primary** node, start a PostgreSQL console session:
```bash
- sudo gitlab-psql
+ sudo gitlab-psql
```
-
+
NOTE: **Note:**
Using `gitlab-rails dbconsole` will not work, because managing replication slots requires superuser permissions.
@@ -43,9 +43,9 @@ Once GitLab has been uninstalled from the **secondary** node, the replication sl
```sql
SELECT * FROM pg_replication_slots;
```
-
+
1. Remove the replication slot for the **secondary** node:
```sql
SELECT pg_drop_replication_slot('<name_of_slot>');
- ```
+ ```
diff --git a/doc/administration/geo/replication/updating_the_geo_nodes.md b/doc/administration/geo/replication/updating_the_geo_nodes.md
index c27f6c78455..166ee94eca4 100644
--- a/doc/administration/geo/replication/updating_the_geo_nodes.md
+++ b/doc/administration/geo/replication/updating_the_geo_nodes.md
@@ -14,6 +14,16 @@ all you need to do is update GitLab itself:
the tracking database is enabled.
1. [Test](#check-status-after-updating) **primary** and **secondary** nodes, and check version in each.
+## 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.
+
+This can be temporarily disabled by running the following before ugprading:
+
+```sh
+sudo touch /etc/gitlab/disable-postgresql-upgrade
+```
+
## Upgrading to GitLab 10.8
Before 10.8, broadcast messages would not propagate without flushing the cache on the **secondary** nodes. This has been fixed in 10.8, but requires one last cache flush on each **secondary** node:
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 4407facfca9..0f547ef03bf 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -2,45 +2,35 @@
[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.
@@ -53,76 +43,78 @@ 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)
+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
-
-Gitaly network traffic is unencrypted by default, but supports
-[TLS](#tls-support). Authentication is done through a static token.
-
-NOTE: **Note:** Gitaly network traffic is unencrypted so we recommend a firewall to
-restrict access to your Gitaly server.
+The following list depicts what the network architecture of Gitaly is:
+
+- 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.
+- 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.
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`.
-### Installation
+### 1. Installation
-First install Gitaly using either Omnibus or from source.
+First install Gitaly using either Omnibus GitLab or install it from source:
-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.
+- 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).
-Source: [Install Gitaly](../../install/installation.md#install-gitaly)
+### 2. Client side token configuration
-### Client side token configuration
+Configure a token on the instance that runs the GitLab Rails application.
-Configure a token on the client side.
+**For Omnibus GitLab**
-Omnibus installations:
+1. On the client node(s), edit `/etc/gitlab/gitlab.rb`:
-```ruby
-# /etc/gitlab/gitlab.rb
-gitlab_rails['gitaly_token'] = 'abc123secret'
-```
+ ```ruby
+ gitlab_rails['gitaly_token'] = 'abc123secret'
+ ```
-Source installations:
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-```yaml
-# /home/git/gitlab/config/gitlab.yml
-gitlab:
- gitaly:
- token: 'abc123secret'
-```
+**For installations from source**
+
+1. On the client node(s), edit `/home/git/gitlab/config/gitlab.yml`:
+
+ ```yaml
+ gitlab:
+ gitaly:
+ token: 'abc123secret'
+ ```
-You need to reconfigure (Omnibus) or restart (source) for these
-changes to be picked up.
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
-### Gitaly server configuration
+### 3. Gitaly server configuration
-Next, on the Gitaly server, we need to configure storage paths, enable
+Next, on the Gitaly server, 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,88 +129,99 @@ 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.
-Source installations:
+**For Omnibus GitLab**
-```toml
-# /home/git/gitaly/config.toml
-listen_addr = '0.0.0.0:8075'
-tls_listen_addr = '0.0.0.0:9999'
+1. Edit `/etc/gitlab/gitlab.rb`:
-[tls]
-certificate_path = /path/to/cert.pem
-key_path = /path/to/key.pem
+ <!--
+ 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
+ -->
-[auth]
-token = 'abc123secret'
+ ```ruby
+ # /etc/gitlab/gitlab.rb
-[[storage]]
-name = 'default'
-path = '/mnt/gitlab/default/repositories'
+ # 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
-[[storage]]
-name = 'storage1'
-path = '/mnt/gitlab/storage1/repositories'
-```
+ # 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'
-Again, reconfigure (Omnibus) or restart (source).
+ # 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'
-### Converting clients to use the Gitaly server
+ gitaly['storage'] = [
+ { 'name' => 'default' },
+ { 'name' => 'storage1' },
+ ]
-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.
+ # 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 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'
+
+ [[storage]]
+ name = 'default'
+
+ [[storage]]
+ name = 'storage1'
+ ```
+
+ 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.
Additionally, you need to
[disable Rugged if previously manually enabled](../high_availability/nfs.md#improving-nfs-performance-with-gitlab).
@@ -227,41 +230,93 @@ 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.
-Omnibus installations:
+**For Omnibus GitLab**
-```ruby
-# /etc/gitlab/gitlab.rb
-git_data_dirs({
- 'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
- 'storage1' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
-})
+1. Edit `/etc/gitlab/gitlab.rb`:
-gitlab_rails['gitaly_token'] = 'abc123secret'
-```
+ ```ruby
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
+ 'storage1' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
+ })
-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'
-```
+ gitlab_rails['gitaly_token'] = 'abc123secret'
+ ```
-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.
+ NOTE: **Note:**
+ In some cases, you'll have to set `path` for each `git_data_dirs` in the
+ format `'path' => '/mnt/gitlab/<storage name>'`.
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Tail the logs to see the requests:
+
+ ```sh
+ sudo gitlab-ctl tail gitaly
+ ```
+
+**For installations from source**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml`:
+
+ ```yaml
+ gitlab:
+ repositories:
+ storages:
+ default:
+ gitaly_address: tcp://gitaly.internal:8075
+ storage1:
+ 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:
+
+ ```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 +326,128 @@ 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://gitaly.internal:9999' },
+ 'storage1' => { 'gitaly_address' => 'tls://gitaly.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://gitaly.internal:9999
+ storage1:
+ gitaly_address: tls://gitaly.internal:9999
- gitaly:
- token: 'abc123secret'
-```
+ gitaly:
+ token: 'abc123secret'
+ ```
-#### On Gitaly server nodes:
+ 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`.
-```toml
-# /home/git/gitaly/config.toml
-tls_listen_addr = '0.0.0.0:9999'
+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`:
-[tls]
-certificate_path = '/path/to/cert.pem'
-key_path = '/path/to/key.pem'
-```
+ ```toml
+ tls_listen_addr = '0.0.0.0:9999'
-## Gitaly-ruby
+ [tls]
+ certificate_path = '/path/to/cert.pem'
+ key_path = '/path/to/key.pem'
+ ```
-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.
-
-### Number of gitaly-ruby workers
-
-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.
-
-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 is a somewhat hidden, internal implementation detail of
-Gitaly. There is not that much visibility into what goes on inside
-gitaly-ruby processes.
+## `gitaly-ruby`
-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.
+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.
-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.
+### Number of `gitaly-ruby` workers
-```
-sum(rate(grpc_client_handled_total[5m])) by (grpc_method) > 0
-```
+`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.
-## Disabling or enabling the Gitaly service in a cluster environment
+You can increase the number of `gitaly-ruby` processes on your Gitaly
+server with the following settings.
-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.
+**For Omnibus GitLab**
-> '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.
+1. Edit `/etc/gitlab/gitlab.rb`:
-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`:
+ ```ruby
+ # Default is 2 workers. The minimum is 2; 1 worker is always reserved as
+ # a passive stand-by.
+ gitaly['ruby_num_workers'] = 4
+ ```
-```ruby
-gitaly['enable'] = false
-```
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-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 +455,23 @@ 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-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 +481,32 @@ 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
+```
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index e81d2741082..41ef68f5b57 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -1,3 +1,7 @@
+---
+type: reference, concepts
+---
+
# Scaling and High Availability
GitLab supports several different types of clustering and high-availability.
@@ -162,14 +166,14 @@ contention due to certain workloads.
- **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),
+- **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.
+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
@@ -211,4 +215,3 @@ separately:
1. [Configure the GitLab application servers](gitlab.md)
1. [Configure the load balancers](load_balancer.md)
1. [Monitoring node (Prometheus and Grafana)](monitoring_node.md)
-
diff --git a/doc/administration/high_availability/alpha_database.md b/doc/administration/high_availability/alpha_database.md
index 7bf20be60e6..7afd739f44c 100644
--- a/doc/administration/high_availability/alpha_database.md
+++ b/doc/administration/high_availability/alpha_database.md
@@ -2,5 +2,4 @@
redirect_to: 'database.md'
---
-This documentation has been moved to the main
-[database documentation](database.md#configure_using_omnibus_for_high_availability).
+This document was moved to [another location](database.md).
diff --git a/doc/administration/high_availability/consul.md b/doc/administration/high_availability/consul.md
index 49199b659bc..b02a61b9256 100644
--- a/doc/administration/high_availability/consul.md
+++ b/doc/administration/high_availability/consul.md
@@ -1,6 +1,8 @@
-# Working with the bundled Consul service **(PREMIUM ONLY)**
+---
+type: reference
+---
-## Overview
+# Working with the bundled Consul service **(PREMIUM ONLY)**
As part of its High Availability stack, GitLab Premium includes a bundled version of [Consul](https://www.consul.io/) that can be managed through `/etc/gitlab/gitlab.rb`.
@@ -26,27 +28,27 @@ On each Consul node perform the following:
1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
- ```ruby
- # Disable all components except Consul
- roles ['consul_role']
+ ```ruby
+ # Disable all components except Consul
+ roles ['consul_role']
- # START user configuration
- # Replace placeholders:
- #
- # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
- # with the addresses gathered for CONSUL_SERVER_NODES
- consul['configuration'] = {
- server: true,
- retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
- }
+ # START user configuration
+ # Replace placeholders:
+ #
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses gathered for CONSUL_SERVER_NODES
+ consul['configuration'] = {
+ server: true,
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
+ }
- # Disable auto migrations
- gitlab_rails['auto_migrate'] = false
- #
- # END user configuration
- ```
+ # Disable auto migrations
+ gitlab_rails['auto_migrate'] = false
+ #
+ # END user configuration
+ ```
- > `consul_role` was introduced with GitLab 10.3
+ > `consul_role` was introduced with GitLab 10.3
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes
to take effect.
@@ -77,6 +79,7 @@ check the [Troubleshooting section](#troubleshooting) before proceeding.
### Checking cluster membership
To see which nodes are part of the cluster, run the following on any member in the cluster
+
```
# /opt/gitlab/embedded/bin/consul members
Node Address Status Type Build Protocol DC
@@ -112,18 +115,18 @@ You will see messages like the following in `gitlab-ctl tail consul` output if y
2017-09-25_19:53:41.74356 2017/09/25 19:53:41 [ERR] agent: failed to sync remote state: No cluster leader
```
-
To fix this:
1. Pick an address on each node that all of the other nodes can reach this node through.
1. Update your `/etc/gitlab/gitlab.rb`
- ```ruby
- consul['configuration'] = {
- ...
- bind_addr: 'IP ADDRESS'
- }
- ```
+ ```ruby
+ consul['configuration'] = {
+ ...
+ bind_addr: 'IP ADDRESS'
+ }
+ ```
+
1. Run `gitlab-ctl reconfigure`
If you still see the errors, you may have to [erase the consul database and reinitialize](#recreate-from-scratch) on the affected node.
@@ -144,12 +147,13 @@ To fix this:
1. Pick an address on the node that all of the other nodes can reach this node through.
1. Update your `/etc/gitlab/gitlab.rb`
- ```ruby
- consul['configuration'] = {
- ...
- bind_addr: 'IP ADDRESS'
- }
- ```
+ ```ruby
+ consul['configuration'] = {
+ ...
+ bind_addr: 'IP ADDRESS'
+ }
+ ```
+
1. Run `gitlab-ctl reconfigure`
### Outage recovery
@@ -157,6 +161,7 @@ To fix this:
If you lost enough server agents in the cluster to break quorum, then the cluster is considered failed, and it will not function without manual intervenetion.
#### Recreate from scratch
+
By default, GitLab does not store anything in the consul cluster that cannot be recreated. To erase the consul database and reinitialize
```
@@ -168,4 +173,5 @@ By default, GitLab does not store anything in the consul cluster that cannot be
After this, the cluster should start back up, and the server agents rejoin. Shortly after that, the client agents should rejoin as well.
#### Recover a failed cluster
+
If you have taken advantage of consul to store other data, and want to restore the failed cluster, please follow the [Consul guide](https://www.consul.io/docs/guides/outage.html) to recover a failed cluster.
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index c32a73080ff..f7a1f425b40 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -1,5 +1,12 @@
+---
+type: reference
+---
+
# Configuring PostgreSQL for Scaling and High Availability
+In this section, you'll be guided through configuring a PostgreSQL database
+to be used with GitLab in a highly available environment.
+
## Provide your own PostgreSQL instance **(CORE ONLY)**
If you're hosting GitLab on a cloud provider, you can optionally use a
@@ -33,15 +40,15 @@ deploy the bundled PostgreSQL.
1. SSH into the PostgreSQL server.
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.
+ - Do not complete any other steps on the download page.
1. Generate a password hash for PostgreSQL. This assumes you will use the default
username of `gitlab` (recommended). The command will request a password
and confirmation. Use the value that is output by this command in the next
step as the value of `POSTGRESQL_PASSWORD_HASH`.
- ```sh
- sudo gitlab-ctl pg-password-md5 gitlab
- ```
+ ```sh
+ sudo gitlab-ctl pg-password-md5 gitlab
+ ```
1. Edit `/etc/gitlab/gitlab.rb` and add the contents below, updating placeholder
values appropriately.
@@ -51,32 +58,32 @@ deploy the bundled PostgreSQL.
addresses of the GitLab application servers that will connect to the
database. Example: `%w(123.123.123.123/32 123.123.123.234/32)`
- ```ruby
- # Disable all components except PostgreSQL
- roles ['postgres_role']
- repmgr['enable'] = false
- consul['enable'] = false
- prometheus['enable'] = false
- alertmanager['enable'] = false
- pgbouncer_exporter['enable'] = false
- redis_exporter['enable'] = false
- gitlab_monitor['enable'] = false
-
- postgresql['listen_address'] = '0.0.0.0'
- postgresql['port'] = 5432
-
- # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
- postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
-
- # Replace XXX.XXX.XXX.XXX/YY with Network Address
- # ????
- postgresql['trust_auth_cidr_addresses'] = %w(APPLICATION_SERVER_IP_BLOCKS)
-
- # Disable automatic database migrations
- gitlab_rails['auto_migrate'] = false
- ```
+ ```ruby
+ # Disable all components except PostgreSQL
+ roles ['postgres_role']
+ repmgr['enable'] = false
+ consul['enable'] = false
+ prometheus['enable'] = false
+ alertmanager['enable'] = false
+ pgbouncer_exporter['enable'] = false
+ redis_exporter['enable'] = false
+ gitlab_monitor['enable'] = false
+
+ postgresql['listen_address'] = '0.0.0.0'
+ postgresql['port'] = 5432
+
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
+
+ # Replace XXX.XXX.XXX.XXX/YY with Network Address
+ # ????
+ postgresql['trust_auth_cidr_addresses'] = %w(APPLICATION_SERVER_IP_BLOCKS)
+
+ # Disable automatic database migrations
+ gitlab_rails['auto_migrate'] = false
+ ```
- NOTE: **Note:** The role `postgres_role` was introduced with GitLab 10.3
+ NOTE: **Note:** The role `postgres_role` was introduced with GitLab 10.3
1. [Reconfigure GitLab] for the changes to take effect.
1. Note the PostgreSQL node's IP address or hostname, port, and
@@ -194,9 +201,9 @@ When using default setup, minimum configuration requires:
- `CONSUL_PASSWORD_HASH`. This is a hash generated out of consul username/password pair.
Can be generated with:
- ```sh
- sudo gitlab-ctl pg-password-md5 CONSUL_USERNAME
- ```
+ ```sh
+ sudo gitlab-ctl pg-password-md5 CONSUL_USERNAME
+ ```
- `CONSUL_SERVER_NODES`. The IP addresses or DNS records of the Consul server nodes.
@@ -237,9 +244,9 @@ We will need the following password information for the application's database u
- `POSTGRESQL_PASSWORD_HASH`. This is a hash generated out of the username/password pair.
Can be generated with:
- ```sh
- sudo gitlab-ctl pg-password-md5 POSTGRESQL_USERNAME
- ```
+ ```sh
+ sudo gitlab-ctl pg-password-md5 POSTGRESQL_USERNAME
+ ```
##### Pgbouncer information
@@ -250,9 +257,9 @@ When using default setup, minimum configuration requires:
- `PGBOUNCER_PASSWORD_HASH`. This is a hash generated out of pgbouncer username/password pair.
Can be generated with:
- ```sh
- sudo gitlab-ctl pg-password-md5 PGBOUNCER_USERNAME
- ```
+ ```sh
+ sudo gitlab-ctl pg-password-md5 PGBOUNCER_USERNAME
+ ```
- `PGBOUNCER_NODE`, is the IP address or a FQDN of the node running Pgbouncer.
@@ -288,7 +295,6 @@ Make sure you install the necessary dependencies from step 1,
add GitLab package repository from step 2.
When installing the GitLab package, do not supply `EXTERNAL_URL` value.
-
#### Configuring the Database nodes
1. Make sure to [configure the Consul nodes](consul.md).
@@ -296,53 +302,54 @@ When installing the GitLab package, do not supply `EXTERNAL_URL` value.
1. On the master database node, edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
- ```ruby
- # Disable all components except PostgreSQL and Repmgr and Consul
- roles ['postgres_role']
-
- # PostgreSQL configuration
- postgresql['listen_address'] = '0.0.0.0'
- postgresql['hot_standby'] = 'on'
- postgresql['wal_level'] = 'replica'
- postgresql['shared_preload_libraries'] = 'repmgr_funcs'
-
- # Disable automatic database migrations
- gitlab_rails['auto_migrate'] = false
-
- # Configure the consul agent
- consul['services'] = %w(postgresql)
-
- # START user configuration
- # Please set the real values as explained in Required Information section
- #
- # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
- postgresql['pgbouncer_user_password'] = 'PGBOUNCER_PASSWORD_HASH'
- # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
- postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
- # Replace X with value of number of db nodes + 1
- postgresql['max_wal_senders'] = X
-
- # Replace XXX.XXX.XXX.XXX/YY with Network Address
- postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY)
- repmgr['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 XXX.XXX.XXX.XXX/YY)
-
- # 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
- ```
-
- > `postgres_role` was introduced with GitLab 10.3
+ ```ruby
+ # Disable all components except PostgreSQL and Repmgr and Consul
+ roles ['postgres_role']
+
+ # PostgreSQL configuration
+ postgresql['listen_address'] = '0.0.0.0'
+ postgresql['hot_standby'] = 'on'
+ postgresql['wal_level'] = 'replica'
+ postgresql['shared_preload_libraries'] = 'repmgr_funcs'
+
+ # Disable automatic database migrations
+ gitlab_rails['auto_migrate'] = false
+
+ # Configure the consul agent
+ consul['services'] = %w(postgresql)
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ #
+ # Replace PGBOUNCER_PASSWORD_HASH with a generated md5 value
+ postgresql['pgbouncer_user_password'] = 'PGBOUNCER_PASSWORD_HASH'
+ # Replace POSTGRESQL_PASSWORD_HASH with a generated md5 value
+ postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
+ # Replace X with value of number of db nodes + 1
+ postgresql['max_wal_senders'] = X
+
+ # Replace XXX.XXX.XXX.XXX/YY with Network Address
+ postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY)
+ repmgr['trust_auth_cidr_addresses'] = %w(127.0.0.1/32 XXX.XXX.XXX.XXX/YY)
+
+ # 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
+ ```
+
+ > `postgres_role` was introduced with GitLab 10.3
1. On secondary nodes, add all the configuration specified above for primary node
to `/etc/gitlab/gitlab.rb`. In addition, append the following configuration
to inform gitlab-ctl that they are standby nodes initially and it need not
attempt to register them as primary node
+
```
# HA setting to specify if a node should attempt to be master on initialization
repmgr['master_on_initialization'] = false
@@ -367,31 +374,31 @@ Select one node as a primary node.
1. Open a database prompt:
- ```sh
- gitlab-psql -d gitlabhq_production
- ```
+ ```sh
+ gitlab-psql -d gitlabhq_production
+ ```
1. Enable the `pg_trgm` extension:
- ```sh
- CREATE EXTENSION pg_trgm;
- ```
+ ```sh
+ CREATE EXTENSION pg_trgm;
+ ```
1. Exit the database prompt by typing `\q` and Enter.
1. Verify the cluster is initialized with one node:
- ```sh
- gitlab-ctl repmgr cluster show
- ```
+ ```sh
+ gitlab-ctl repmgr cluster show
+ ```
- The output should be similar to the following:
+ The output should be similar to the following:
- ```
- Role | Name | Upstream | Connection String
- ----------+----------|----------|----------------------------------------
- * master | HOSTNAME | | host=HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr
- ```
+ ```
+ Role | Name | Upstream | Connection String
+ ----------+----------|----------|----------------------------------------
+ * master | HOSTNAME | | host=HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr
+ ```
1. Note down the hostname/ip in the connection string: `host=HOSTNAME`. We will
refer to the hostname in the next section as `MASTER_NODE_NAME`. If the value
@@ -402,43 +409,43 @@ Select one node as a primary node.
1. Set up the repmgr standby:
- ```sh
- gitlab-ctl repmgr standby setup MASTER_NODE_NAME
- ```
-
- Do note that this will remove the existing data on the node. The command
- has a wait time.
-
- The output should be similar to the following:
-
- ```console
- # gitlab-ctl repmgr standby setup MASTER_NODE_NAME
- Doing this will delete the entire contents of /var/opt/gitlab/postgresql/data
- If this is not what you want, hit Ctrl-C now to exit
- To skip waiting, rerun with the -w option
- Sleeping for 30 seconds
- Stopping the database
- Removing the data
- Cloning the data
- Starting the database
- Registering the node with the cluster
- ok: run: repmgrd: (pid 19068) 0s
- ```
+ ```sh
+ gitlab-ctl repmgr standby setup MASTER_NODE_NAME
+ ```
+
+ Do note that this will remove the existing data on the node. The command
+ has a wait time.
+
+ The output should be similar to the following:
+
+ ```console
+ # gitlab-ctl repmgr standby setup MASTER_NODE_NAME
+ Doing this will delete the entire contents of /var/opt/gitlab/postgresql/data
+ If this is not what you want, hit Ctrl-C now to exit
+ To skip waiting, rerun with the -w option
+ Sleeping for 30 seconds
+ Stopping the database
+ Removing the data
+ Cloning the data
+ Starting the database
+ Registering the node with the cluster
+ ok: run: repmgrd: (pid 19068) 0s
+ ```
1. Verify the node now appears in the cluster:
- ```sh
- gitlab-ctl repmgr cluster show
- ```
+ ```sh
+ gitlab-ctl repmgr cluster show
+ ```
- The output should be similar to the following:
+ The output should be similar to the following:
- ```
- Role | Name | Upstream | Connection String
- ----------+---------|-----------|------------------------------------------------
- * master | MASTER | | host=MASTER_NODE_NAME user=gitlab_repmgr dbname=gitlab_repmgr
- standby | STANDBY | MASTER | host=STANDBY_HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr
- ```
+ ```
+ Role | Name | Upstream | Connection String
+ ----------+---------|-----------|------------------------------------------------
+ * master | MASTER | | host=MASTER_NODE_NAME user=gitlab_repmgr dbname=gitlab_repmgr
+ standby | STANDBY | MASTER | host=STANDBY_HOSTNAME user=gitlab_repmgr dbname=gitlab_repmgr
+ ```
Repeat the above steps on all secondary nodes.
@@ -478,88 +485,7 @@ Check the [Troubleshooting section](#troubleshooting) before proceeding.
#### Configuring the Pgbouncer node
-1. Make sure you collect [`CONSUL_SERVER_NODES`](#consul-information), [`CONSUL_PASSWORD_HASH`](#consul-information), and [`PGBOUNCER_PASSWORD_HASH`](#pgbouncer-information) before executing the next step.
-
-1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
-
- ```ruby
- # Disable all components except Pgbouncer and Consul agent
- roles ['pgbouncer_role']
-
- # Configure Pgbouncer
- pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
-
- # Configure Consul agent
- consul['watchers'] = %w(postgresql)
-
- # START user configuration
- # Please set the real values as explained in Required Information section
- # Replace CONSUL_PASSWORD_HASH with with a generated md5 value
- # Replace PGBOUNCER_PASSWORD_HASH with with a generated md5 value
- pgbouncer['users'] = {
- 'gitlab-consul': {
- password: 'CONSUL_PASSWORD_HASH'
- },
- 'pgbouncer': {
- password: 'PGBOUNCER_PASSWORD_HASH'
- }
- }
- # Replace placeholders:
- #
- # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
- # with the addresses gathered for CONSUL_SERVER_NODES
- consul['configuration'] = {
- retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
- }
- #
- # END user configuration
- ```
-
- > `pgbouncer_role` was introduced with GitLab 10.3
-
-1. [Reconfigure GitLab] for the changes to take effect.
-
-1. Create a `.pgpass` file so Consul is able to
- reload pgbouncer. Enter the `PGBOUNCER_PASSWORD` twice when asked:
-
- ```sh
- gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
- ```
-
-##### PGBouncer Checkpoint
-
-1. Ensure the node is talking to the current master:
-
- ```sh
- gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
- ```
-
- If there is an error `psql: ERROR: Auth failed` after typing in the
- password, ensure you previously generated the MD5 password hashes with the correct
- format. The correct format is to concatenate the password and the username:
- `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
- needed to generate an MD5 password hash for the `pgbouncer` user.
-
-1. Once the console prompt is available, run the following queries:
-
- ```sh
- show databases ; show clients ;
- ```
-
- The output should be similar to the following:
-
- ```
- name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
- ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
- gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
- pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
- (2 rows)
-
- type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
- ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
- C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
- (2 rows)
- ```
+See our [documentation for Pgbouncer](pgbouncer.md) for information on running Pgbouncer as part of an HA setup.
#### Configuring the Application nodes
@@ -568,15 +494,15 @@ attributes set, but the following need to be set.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- # Disable PostgreSQL on the application node
- postgresql['enable'] = false
+ ```ruby
+ # Disable PostgreSQL on the application node
+ postgresql['enable'] = false
- gitlab_rails['db_host'] = 'PGBOUNCER_NODE'
- gitlab_rails['db_port'] = 6432
- gitlab_rails['db_password'] = 'POSTGRESQL_USER_PASSWORD'
- gitlab_rails['auto_migrate'] = false
- ```
+ gitlab_rails['db_host'] = 'PGBOUNCER_NODE'
+ gitlab_rails['db_port'] = 6432
+ gitlab_rails['db_password'] = 'POSTGRESQL_USER_PASSWORD'
+ gitlab_rails['auto_migrate'] = false
+ ```
1. [Reconfigure GitLab] for the changes to take effect.
@@ -736,45 +662,45 @@ After deploying the configuration follow these steps:
1. On `10.6.0.21`, our primary database
- Enable the `pg_trgm` extension
+ Enable the `pg_trgm` extension
- ```sh
- gitlab-psql -d gitlabhq_production
- ```
+ ```sh
+ gitlab-psql -d gitlabhq_production
+ ```
- ```
- CREATE EXTENSION pg_trgm;
- ```
+ ```
+ CREATE EXTENSION pg_trgm;
+ ```
1. On `10.6.0.22`, our first standby database
- Make this node a standby of the primary
+ Make this node a standby of the primary
- ```sh
- gitlab-ctl repmgr standby setup 10.6.0.21
- ```
+ ```sh
+ gitlab-ctl repmgr standby setup 10.6.0.21
+ ```
1. On `10.6.0.23`, our second standby database
- Make this node a standby of the primary
+ Make this node a standby of the primary
- ```sh
- gitlab-ctl repmgr standby setup 10.6.0.21
- ```
+ ```sh
+ gitlab-ctl repmgr standby setup 10.6.0.21
+ ```
1. On `10.6.0.31`, our application server
- Set gitlab-consul's pgbouncer password to `toomanysecrets`
+ Set gitlab-consul's pgbouncer password to `toomanysecrets`
- ```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
+ ```
- Run database migrations
+ Run database migrations
- ```sh
- gitlab-rake gitlab:db:configure
- ```
+ ```sh
+ gitlab-rake gitlab:db:configure
+ ```
#### Example minimal setup
@@ -911,16 +837,16 @@ standby nodes.
1. Ensure the old master node is not still active.
1. Login to the server that should become the new master and run:
- ```sh
- gitlab-ctl repmgr standby promote
- ```
+ ```sh
+ gitlab-ctl repmgr standby promote
+ ```
1. If there are any other standby servers in the cluster, have them follow
the new master server:
- ```sh
- gitlab-ctl repmgr standby follow NEW_MASTER
- ```
+ ```sh
+ gitlab-ctl repmgr standby follow NEW_MASTER
+ ```
#### Restore procedure
@@ -930,42 +856,42 @@ after it has been restored to service.
- If you want to remove the node from the cluster, on any other node in the
cluster, run:
- ```sh
- gitlab-ctl repmgr standby unregister --node=X
- ```
+ ```sh
+ gitlab-ctl repmgr standby unregister --node=X
+ ```
- where X is the value of node in `repmgr.conf` on the old server.
+ where X is the value of node in `repmgr.conf` on the old server.
- To find this, you can use:
+ To find this, you can use:
- ```sh
- awk -F = '$1 == "node" { print $2 }' /var/opt/gitlab/postgresql/repmgr.conf
- ```
+ ```sh
+ awk -F = '$1 == "node" { print $2 }' /var/opt/gitlab/postgresql/repmgr.conf
+ ```
- It will output something like:
+ It will output something like:
- ```
- 959789412
- ```
+ ```
+ 959789412
+ ```
- Then you will use this id to unregister the node:
+ Then you will use this id to unregister the node:
- ```sh
- gitlab-ctl repmgr standby unregister --node=959789412
- ```
+ ```sh
+ gitlab-ctl repmgr standby unregister --node=959789412
+ ```
- To add the node as a standby server:
- ```sh
- gitlab-ctl repmgr standby follow NEW_MASTER
- gitlab-ctl restart repmgrd
- ```
+ ```sh
+ gitlab-ctl repmgr standby follow NEW_MASTER
+ gitlab-ctl restart repmgrd
+ ```
- CAUTION: **Warning:** When the server is brought back online, and before
- you switch it to a standby node, repmgr will report that there are two masters.
- If there are any clients that are still attempting to write to the old master,
- this will cause a split, and the old master will need to be resynced from
- scratch by performing a `gitlab-ctl repmgr standby setup NEW_MASTER`.
+ CAUTION: **Warning:** When the server is brought back online, and before
+ you switch it to a standby node, repmgr will report that there are two masters.
+ If there are any clients that are still attempting to write to the old master,
+ this will cause a split, and the old master will need to be resynced from
+ scratch by performing a `gitlab-ctl repmgr standby setup NEW_MASTER`.
#### Alternate configurations
@@ -1008,13 +934,13 @@ the previous section:
1. On the current master node, create a password for the `gitlab` and
`gitlab_repmgr` user:
- ```sh
- gitlab-psql -d template1
- template1=# \password gitlab_repmgr
- Enter password: ****
- Confirm password: ****
- template1=# \password gitlab
- ```
+ ```sh
+ gitlab-psql -d template1
+ template1=# \password gitlab_repmgr
+ Enter password: ****
+ Confirm password: ****
+ template1=# \password gitlab
+ ```
1. On each database node:
@@ -1028,9 +954,9 @@ the previous section:
1. Create a `.pgpass` file. Enter the `gitlab_repmgr` password twice to
when asked:
- ```sh
- gitlab-ctl write-pgpass --user gitlab_repmgr --hostuser gitlab-psql --database '*'
- ```
+ ```sh
+ gitlab-ctl write-pgpass --user gitlab_repmgr --hostuser gitlab-psql --database '*'
+ ```
1. On each pgbouncer node, edit `/etc/gitlab/gitlab.rb`:
1. Ensure `gitlab_rails['db_password']` is set to the plaintext password for
@@ -1058,7 +984,7 @@ If you enable Monitoring, it must be enabled on **all** database servers.
## Troubleshooting
-### Consul and PostgreSQL changes not taking effect.
+### Consul and PostgreSQL changes not taking effect
Due to the potential impacts, `gitlab-ctl reconfigure` only reloads Consul and PostgreSQL, it will not restart the services. However, not all changes can be activated by reloading.
diff --git a/doc/administration/high_availability/gitaly.md b/doc/administration/high_availability/gitaly.md
index b7eaa4ce105..739d1ae35fb 100644
--- a/doc/administration/high_availability/gitaly.md
+++ b/doc/administration/high_availability/gitaly.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring Gitaly for Scaled and High Availability
Gitaly does not yet support full high availability. However, Gitaly is quite
@@ -46,3 +50,15 @@ Continue configuration of other components by going back to:
```
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
+
+<!-- ## 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/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 9b1b7142e83..8818a9606de 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -1,4 +1,8 @@
-# Configuring GitLab Scaling and High Availability
+---
+type: reference
+---
+
+# Configuring GitLab for Scaling and High Availability
> **Note:** There is some additional configuration near the bottom for
additional GitLab application servers. It's important to read and understand
@@ -7,33 +11,33 @@
1. If necessary, install the NFS client utility packages using the following
commands:
- ```
- # Ubuntu/Debian
- apt-get install nfs-common
+ ```
+ # Ubuntu/Debian
+ apt-get install nfs-common
- # CentOS/Red Hat
- yum install nfs-utils nfs-utils-lib
- ```
+ # CentOS/Red Hat
+ yum install nfs-utils nfs-utils-lib
+ ```
1. Specify the necessary NFS shares. Mounts are specified in
`/etc/fstab`. The exact contents of `/etc/fstab` will depend on how you chose
to configure your NFS server. See [NFS documentation](nfs.md) for the various
options. Here is an example snippet to add to `/etc/fstab`:
- ```
- 10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
- 10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
- 10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
- 10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
- 10.1.0.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
- ```
+ ```
+ 10.1.0.1:/var/opt/gitlab/.ssh /var/opt/gitlab/.ssh nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/uploads nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-rails/shared nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/gitlab-ci/builds nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
+ 10.1.0.1:/var/opt/gitlab/git-data /var/opt/gitlab/git-data nfs4 defaults,soft,rsize=1048576,wsize=1048576,noatime,nofail,lookupcache=positive 0 2
+ ```
1. Create the shared directories. These may be different depending on your NFS
mount locations.
- ```
- mkdir -p /var/opt/gitlab/.ssh /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/git-data
- ```
+ ```
+ mkdir -p /var/opt/gitlab/.ssh /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/git-data
+ ```
1. Download/install GitLab Omnibus using **steps 1 and 2** from
[GitLab downloads](https://about.gitlab.com/downloads). Do not complete other
@@ -46,52 +50,52 @@
added NFS mounts in the default data locations. Additionally the UID and GIDs
given are just examples and you should configure with your preferred values.
- ```ruby
- external_url 'https://gitlab.example.com'
-
- # Prevent GitLab from starting if NFS data mounts are not available
- high_availability['mountpoint'] = '/var/opt/gitlab/git-data'
-
- # Disable components that will not be on the GitLab application server
- roles ['application_role']
- nginx['enable'] = true
-
- # PostgreSQL connection details
- gitlab_rails['db_adapter'] = 'postgresql'
- gitlab_rails['db_encoding'] = 'unicode'
- gitlab_rails['db_host'] = '10.1.0.5' # IP/hostname of database server
- gitlab_rails['db_password'] = 'DB password'
-
- # Redis connection details
- gitlab_rails['redis_port'] = '6379'
- gitlab_rails['redis_host'] = '10.1.0.6' # IP/hostname of Redis server
- gitlab_rails['redis_password'] = 'Redis Password'
-
- # Ensure UIDs and GIDs match between servers for permissions via NFS
- user['uid'] = 9000
- user['gid'] = 9000
- web_server['uid'] = 9001
- web_server['gid'] = 9001
- registry['uid'] = 9002
- registry['gid'] = 9002
- ```
+ ```ruby
+ external_url 'https://gitlab.example.com'
+
+ # Prevent GitLab from starting if NFS data mounts are not available
+ high_availability['mountpoint'] = '/var/opt/gitlab/git-data'
+
+ # Disable components that will not be on the GitLab application server
+ roles ['application_role']
+ nginx['enable'] = true
+
+ # PostgreSQL connection details
+ gitlab_rails['db_adapter'] = 'postgresql'
+ gitlab_rails['db_encoding'] = 'unicode'
+ gitlab_rails['db_host'] = '10.1.0.5' # IP/hostname of database server
+ gitlab_rails['db_password'] = 'DB password'
+
+ # Redis connection details
+ gitlab_rails['redis_port'] = '6379'
+ gitlab_rails['redis_host'] = '10.1.0.6' # IP/hostname of Redis server
+ gitlab_rails['redis_password'] = 'Redis Password'
+
+ # Ensure UIDs and GIDs match between servers for permissions via NFS
+ user['uid'] = 9000
+ user['gid'] = 9000
+ web_server['uid'] = 9001
+ web_server['gid'] = 9001
+ registry['uid'] = 9002
+ registry['gid'] = 9002
+ ```
1. [Enable monitoring](#enable-monitoring)
- > **Note:** To maintain uniformity of links across HA clusters, the `external_url`
- on the first application server as well as the additional application
- servers should point to the external url that users will use to access GitLab.
- In a typical HA setup, this will be the url of the load balancer which will
- route traffic to all GitLab application servers in the HA cluster.
- >
- > **Note:** When you specify `https` in the `external_url`, as in the example
- above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
- certificates are not present, Nginx will fail to start. See
- [Nginx documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
- for more information.
- >
- > **Note:** It is best to set the `uid` and `gid`s prior to the initial reconfigure
- of GitLab. Omnibus will not recursively `chown` directories if set after the initial reconfigure.
+ > **Note:** To maintain uniformity of links across HA clusters, the `external_url`
+ on the first application server as well as the additional application
+ servers should point to the external url that users will use to access GitLab.
+ In a typical HA setup, this will be the url of the load balancer which will
+ route traffic to all GitLab application servers in the HA cluster.
+ >
+ > **Note:** When you specify `https` in the `external_url`, as in the example
+ above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If
+ certificates are not present, Nginx will fail to start. See
+ [Nginx documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
+ for more information.
+ >
+ > **Note:** It is best to set the `uid` and `gid`s prior to the initial reconfigure
+ of GitLab. Omnibus will not recursively `chown` directories if set after the initial reconfigure.
## First GitLab application server
@@ -114,12 +118,12 @@ need some extra configuration.
secondary servers **prior to** running the first `reconfigure` in the steps
above.
- ```ruby
- gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860'
- gitlab_rails['otp_key_base'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa'
- gitlab_rails['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d'
- gitlab_rails['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964'
- ```
+ ```ruby
+ gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860'
+ gitlab_rails['otp_key_base'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa'
+ gitlab_rails['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d'
+ gitlab_rails['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964'
+ ```
1. Run `touch /etc/gitlab/skip-auto-reconfigure` to prevent database migrations
from running on upgrade. Only the primary GitLab application server should
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
index 28b226cacd5..9e9f604317a 100644
--- a/doc/administration/high_availability/load_balancer.md
+++ b/doc/administration/high_availability/load_balancer.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Load Balancer for GitLab HA
In an active/active GitLab configuration, you will need a load balancer to route
@@ -114,3 +118,15 @@ Read more on high-availability configuration:
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
+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/high_availability/monitoring_node.md b/doc/administration/high_availability/monitoring_node.md
index 385e7441ac9..b91a994d01e 100644
--- a/doc/administration/high_availability/monitoring_node.md
+++ b/doc/administration/high_availability/monitoring_node.md
@@ -1,10 +1,16 @@
+---
+type: reference
+---
+
# Configuring a Monitoring node for Scaling and High Availability
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3786) in GitLab 12.0.
+You can configure a Prometheus node to monitor GitLab.
+
## Standalone Monitoring node using GitLab Omnibus
-The GitLab Omnibus package can be used to configure a standalone Monitoring node running Prometheus and Grafana.
+The GitLab Omnibus package can be used to configure a standalone Monitoring node running [Prometheus](../monitoring/prometheus/index.md) and [Grafana](../monitoring/performance/grafana_configuration.md).
The monitoring node is not highly available. See [Scaling and High Availability](README.md)
for an overview of GitLab scaling and high availability options.
@@ -20,44 +26,44 @@ Omnibus:
1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
- ```ruby
- external_url 'http://gitlab.example.com'
-
- # Enable Prometheus
- prometheus['enable'] = true
- prometheus['listen_address'] = '0.0.0.0:9090'
- prometheus['monitor_kubernetes'] = false
-
- # Enable Grafana
- grafana['enable'] = true
- grafana['admin_password'] = 'toomanysecrets'
-
- # Enable service discovery for Prometheus
- consul['enable'] = true
- consul['monitoring_service_discovery'] = true
-
- # Replace placeholders
- # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
- # with the addresses of the Consul server nodes
- consul['configuration'] = {
- retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z),
- }
-
- # Disable all other services
- gitlab_rails['auto_migrate'] = false
- alertmanager['enable'] = false
- gitaly['enable'] = false
- gitlab_monitor['enable'] = false
- gitlab_workhorse['enable'] = false
- nginx['enable'] = true
- postgres_exporter['enable'] = false
- postgresql['enable'] = false
- redis['enable'] = false
- redis_exporter['enable'] = false
- sidekiq['enable'] = false
- unicorn['enable'] = false
- node_exporter['enable'] = false
- ```
+ ```ruby
+ external_url 'http://gitlab.example.com'
+
+ # Enable Prometheus
+ prometheus['enable'] = true
+ prometheus['listen_address'] = '0.0.0.0:9090'
+ prometheus['monitor_kubernetes'] = false
+
+ # Enable Grafana
+ grafana['enable'] = true
+ grafana['admin_password'] = 'toomanysecrets'
+
+ # Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
+
+ # Replace placeholders
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses of the Consul server nodes
+ consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z),
+ }
+
+ # Disable all other services
+ gitlab_rails['auto_migrate'] = false
+ alertmanager['enable'] = false
+ gitaly['enable'] = false
+ gitlab_monitor['enable'] = false
+ gitlab_workhorse['enable'] = false
+ nginx['enable'] = true
+ postgres_exporter['enable'] = false
+ postgresql['enable'] = false
+ redis['enable'] = false
+ redis_exporter['enable'] = false
+ sidekiq['enable'] = false
+ unicorn['enable'] = false
+ node_exporter['enable'] = false
+ ```
1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
@@ -67,3 +73,15 @@ Once monitoring using Service Discovery is enabled with `consul['monitoring_serv
ensure that `prometheus['scrape_configs']` is not set in `/etc/gitlab/gitlab.rb`. Setting both
`consul['monitoring_service_discovery'] = true` and `prometheus['scrape_configs']` in `/etc/gitlab/gitlab.rb`
will result in errors.
+
+<!-- ## 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/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 561ba214686..294f0e969d5 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# NFS
You can view information and options set for each of the mounted NFS file
@@ -42,34 +46,20 @@ maintaining ID mapping without LDAP, in most cases you should enable numeric UID
and GIDs (which is off by default in some cases) for simplified permission
management between systems:
- - [NetApp instructions](https://library.netapp.com/ecmdocs/ECMP1401220/html/GUID-24367A9F-E17B-4725-ADC1-02D86F56F78E.html)
- - For non-NetApp devices, disable NFSv4 `idmapping` by performing opposite of [enable NFSv4 idmapper](https://wiki.archlinux.org/index.php/NFS#Enabling_NFSv4_idmapping)
+- [NetApp instructions](https://library.netapp.com/ecmdocs/ECMP1401220/html/GUID-24367A9F-E17B-4725-ADC1-02D86F56F78E.html)
+- For non-NetApp devices, disable NFSv4 `idmapping` by performing opposite of [enable NFSv4 idmapper](https://wiki.archlinux.org/index.php/NFS#Enabling_NFSv4_idmapping)
### Improving NFS performance with GitLab
-NOTE: **Note:** This is only available starting in certain versions of GitLab: 11.5.11,
-11.6.11, 11.7.12, 11.8.8, 11.9.0 and up (e.g. 11.10, 11.11, etc.)
+NOTE: **Note:** From GitLab 12.1, it will automatically be detected if Rugged can and should be used per storage.
-If you are using NFS to share Git data, we recommend that you enable a
-number of feature flags that will allow GitLab application processes to
-access Git data directly instead of going through the [Gitaly
-service](../gitaly/index.md). Depending on your workload and disk
-performance, these flags may help improve performance. See [the
-issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/57317) for more
-details.
-
-To do this, run the Rake task:
+If you previously enabled Rugged using the feature flag, you will need to unset the feature flag by using:
```sh
-sudo gitlab-rake gitlab:features:enable_rugged
+sudo gitlab-rake gitlab:features:unset_rugged
```
-If you need to undo this setting for some reason such as switching to [Gitaly without NFS](gitaly.md)
-(recommended), run:
-
-```sh
-sudo gitlab-rake gitlab:features:disable_rugged
-```
+If the Rugged feature flag is explicitly set to either true or false, GitLab will use the value explicitly set.
### Known issues
@@ -87,10 +77,10 @@ on an Linux NFS server, do the following:
1. On the NFS server, run:
- ```sh
- echo 0 > /proc/sys/fs/leases-enable
- sysctl -w fs.leases-enable=0
- ```
+ ```sh
+ echo 0 > /proc/sys/fs/leases-enable
+ sysctl -w fs.leases-enable=0
+ ```
1. Restart the NFS server process. For example, on CentOS run `service nfs restart`.
@@ -236,3 +226,15 @@ Read more on high-availability configuration:
1. [Configure the load balancers](load_balancer.md)
[udp-log-shipping]: https://docs.gitlab.com/omnibus/settings/logs.html#udp-log-shipping-gitlab-enterprise-edition-only "UDP log shipping"
+
+<!-- ## 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/high_availability/nfs_host_client_setup.md b/doc/administration/high_availability/nfs_host_client_setup.md
index a8d69b9ab0a..9b0e085fe25 100644
--- a/doc/administration/high_availability/nfs_host_client_setup.md
+++ b/doc/administration/high_availability/nfs_host_client_setup.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring NFS for GitLab HA
Setting up NFS for a GitLab HA setup allows all applications nodes in a cluster
@@ -133,3 +137,15 @@ client with the command below.
```sh
sudo ufw allow from <client-ip-address> to any port nfs
```
+
+<!-- ## 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/high_availability/pgbouncer.md b/doc/administration/high_availability/pgbouncer.md
index 053dae25823..0b945bc6244 100644
--- a/doc/administration/high_availability/pgbouncer.md
+++ b/doc/administration/high_availability/pgbouncer.md
@@ -1,6 +1,8 @@
-# Working with the bundle Pgbouncer service
+---
+type: reference
+---
-## Overview
+# Working with the bundle Pgbouncer service
As part of its High Availability stack, GitLab Premium includes a bundled version of [Pgbouncer](https://pgbouncer.github.io/) that can be managed through `/etc/gitlab/gitlab.rb`.
@@ -14,7 +16,88 @@ It is recommended to run pgbouncer alongside the `gitlab-rails` service, or on i
### Running Pgbouncer as part of an HA GitLab installation
-See our [HA documentation for PostgreSQL](database.md) for information on running pgbouncer as part of a HA setup
+1. Make sure you collect [`CONSUL_SERVER_NODES`](database.md#consul-information), [`CONSUL_PASSWORD_HASH`](database.md#consul-information), and [`PGBOUNCER_PASSWORD_HASH`](database.md#pgbouncer-information) before executing the next step.
+
+1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
+
+ ```ruby
+ # Disable all components except Pgbouncer and Consul agent
+ roles ['pgbouncer_role']
+
+ # Configure Pgbouncer
+ pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
+
+ # Configure Consul agent
+ consul['watchers'] = %w(postgresql)
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ # Replace CONSUL_PASSWORD_HASH with with a generated md5 value
+ # Replace PGBOUNCER_PASSWORD_HASH with with a generated md5 value
+ pgbouncer['users'] = {
+ 'gitlab-consul': {
+ password: 'CONSUL_PASSWORD_HASH'
+ },
+ 'pgbouncer': {
+ password: 'PGBOUNCER_PASSWORD_HASH'
+ }
+ }
+ # Replace placeholders:
+ #
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses gathered for CONSUL_SERVER_NODES
+ consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
+ }
+ #
+ # END user configuration
+ ```
+
+ > `pgbouncer_role` was introduced with GitLab 10.3
+
+1. Run `gitlab-ctl reconfigure`
+
+1. Create a `.pgpass` file so Consul is able to
+ reload pgbouncer. Enter the `PGBOUNCER_PASSWORD` twice when asked:
+
+ ```sh
+ gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
+ ```
+
+#### PGBouncer Checkpoint
+
+1. Ensure the node is talking to the current master:
+
+ ```sh
+ gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
+ ```
+
+ If there is an error `psql: ERROR: Auth failed` after typing in the
+ password, ensure you previously generated the MD5 password hashes with the correct
+ format. The correct format is to concatenate the password and the username:
+ `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
+ needed to generate an MD5 password hash for the `pgbouncer` user.
+
+1. Once the console prompt is available, run the following queries:
+
+ ```sh
+ show databases ; show clients ;
+ ```
+
+ The output should be similar to the following:
+
+ ```
+ name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
+ ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
+ gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
+ pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
+ (2 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
+ ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
+ C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
+ (2 rows)
+ ```
### Running Pgbouncer as part of a non-HA GitLab installation
@@ -24,39 +107,39 @@ See our [HA documentation for PostgreSQL](database.md) for information on runnin
1. On your database node, ensure the following is set in your `/etc/gitlab/gitlab.rb`
- ```ruby
- postgresql['pgbouncer_user_password'] = 'PGBOUNCER_USER_PASSWORD_HASH'
- postgresql['sql_user_password'] = 'SQL_USER_PASSWORD_HASH'
- postgresql['listen_address'] = 'XX.XX.XX.Y' # Where XX.XX.XX.Y is the ip address on the node postgresql should listen on
- postgresql['md5_auth_cidr_addresses'] = %w(AA.AA.AA.B/32) # Where AA.AA.AA.B is the IP address of the pgbouncer node
- ```
+ ```ruby
+ postgresql['pgbouncer_user_password'] = 'PGBOUNCER_USER_PASSWORD_HASH'
+ postgresql['sql_user_password'] = 'SQL_USER_PASSWORD_HASH'
+ postgresql['listen_address'] = 'XX.XX.XX.Y' # Where XX.XX.XX.Y is the ip address on the node postgresql should listen on
+ postgresql['md5_auth_cidr_addresses'] = %w(AA.AA.AA.B/32) # Where AA.AA.AA.B is the IP address of the pgbouncer node
+ ```
1. Run `gitlab-ctl reconfigure`
- **Note:** If the database was already running, it will need to be restarted after reconfigure by running `gitlab-ctl restart postgresql`.
+ **Note:** If the database was already running, it will need to be restarted after reconfigure by running `gitlab-ctl restart postgresql`.
1. On the node you are running pgbouncer on, make sure the following is set in `/etc/gitlab/gitlab.rb`
- ```ruby
- pgbouncer['enable'] = true
- pgbouncer['databases'] = {
- gitlabhq_production: {
- host: 'DATABASE_HOST',
- user: 'pgbouncer',
- password: 'PGBOUNCER_USER_PASSWORD_HASH'
- }
- }
- ```
+ ```ruby
+ pgbouncer['enable'] = true
+ pgbouncer['databases'] = {
+ gitlabhq_production: {
+ host: 'DATABASE_HOST',
+ user: 'pgbouncer',
+ password: 'PGBOUNCER_USER_PASSWORD_HASH'
+ }
+ }
+ ```
1. Run `gitlab-ctl reconfigure`
1. On the node running unicorn, make sure the following is set in `/etc/gitlab/gitlab.rb`
- ```ruby
- gitlab_rails['db_host'] = 'PGBOUNCER_HOST'
- gitlab_rails['db_port'] = '6432'
- gitlab_rails['db_password'] = 'SQL_USER_PASSWORD'
- ```
+ ```ruby
+ gitlab_rails['db_host'] = 'PGBOUNCER_HOST'
+ gitlab_rails['db_port'] = '6432'
+ gitlab_rails['db_password'] = 'SQL_USER_PASSWORD'
+ ```
1. Run `gitlab-ctl reconfigure`
@@ -66,28 +149,28 @@ See our [HA documentation for PostgreSQL](database.md) for information on runnin
> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3786) in GitLab 12.0.
- If you enable Monitoring, it must be enabled on **all** pgbouncer servers.
+If you enable Monitoring, it must be enabled on **all** pgbouncer servers.
- 1. Create/edit `/etc/gitlab/gitlab.rb` and add the following configuration:
+1. Create/edit `/etc/gitlab/gitlab.rb` and add the following configuration:
- ```ruby
- # Enable service discovery for Prometheus
- consul['enable'] = true
- consul['monitoring_service_discovery'] = true
+ ```ruby
+ # Enable service discovery for Prometheus
+ consul['enable'] = true
+ consul['monitoring_service_discovery'] = true
- # Replace placeholders
- # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
- # with the addresses of the Consul server nodes
- consul['configuration'] = {
- retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z),
- }
+ # Replace placeholders
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses of the Consul server nodes
+ consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z),
+ }
- # Set the network addresses that the exporters will listen on
- node_exporter['listen_address'] = '0.0.0.0:9100'
- pgbouncer_exporter['listen_address'] = '0.0.0.0:9188'
- ```
+ # Set the network addresses that the exporters will listen on
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ pgbouncer_exporter['listen_address'] = '0.0.0.0:9188'
+ ```
- 1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
+1. Run `sudo gitlab-ctl reconfigure` to compile the configuration.
### Interacting with pgbouncer
@@ -109,6 +192,7 @@ pgbouncer=#
The password you will be prompted for is the PGBOUNCER_USER_PASSWORD
To get some basic information about the instance, run
+
```shell
pgbouncer=# show databases; show clients; show servers;
name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index 27310c59755..1b79dde9476 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring Redis for Scaling and High Availability
## Provide your own Redis instance **(CORE ONLY)**
@@ -47,28 +51,28 @@ Omnibus:
1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
- ```ruby
- ## Enable Redis
- redis['enable'] = true
-
- ## Disable all other services
- sidekiq['enable'] = false
- gitlab_workhorse['enable'] = false
- unicorn['enable'] = false
- postgresql['enable'] = false
- nginx['enable'] = false
- prometheus['enable'] = false
- alertmanager['enable'] = false
- pgbouncer_exporter['enable'] = false
- gitlab_monitor['enable'] = false
- gitaly['enable'] = false
-
- redis['bind'] = '0.0.0.0'
- redis['port'] = '6379'
- redis['password'] = 'SECRET_PASSWORD_HERE'
-
- gitlab_rails['auto_migrate'] = false
- ```
+ ```ruby
+ ## Enable Redis
+ redis['enable'] = true
+
+ ## Disable all other services
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ unicorn['enable'] = false
+ postgresql['enable'] = false
+ nginx['enable'] = false
+ prometheus['enable'] = false
+ alertmanager['enable'] = false
+ pgbouncer_exporter['enable'] = false
+ gitlab_monitor['enable'] = false
+ gitaly['enable'] = false
+
+ redis['bind'] = '0.0.0.0'
+ redis['port'] = '6379'
+ redis['password'] = 'SECRET_PASSWORD_HERE'
+
+ gitlab_rails['auto_migrate'] = false
+ ```
1. [Reconfigure Omnibus GitLab][reconfigure] for the changes to take effect.
1. Note the Redis node's IP address or hostname, port, and
@@ -359,37 +363,37 @@ The prerequisites for a HA Redis setup are the following:
1. SSH into the **master** Redis server.
1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
package you want using **steps 1 and 2** from the GitLab downloads page.
- - Make sure you select the correct Omnibus package, with the same version
- and type (Community, Enterprise editions) of your current install.
- - Do not complete any other steps on the download page.
+ - Make sure you select the correct Omnibus package, with the same version
+ and type (Community, Enterprise editions) of your current install.
+ - Do not complete any other steps on the download page.
1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
- ```ruby
- # Specify server role as 'redis_master_role'
- roles ['redis_master_role']
+ ```ruby
+ # Specify server role as 'redis_master_role'
+ roles ['redis_master_role']
- # IP address pointing to a local IP that the other machines can reach to.
- # You can also set bind to '0.0.0.0' which listen in all interfaces.
- # If you really need to bind to an external accessible IP, make
- # sure you add extra firewall rules to prevent unauthorized access.
- redis['bind'] = '10.0.0.1'
+ # IP address pointing to a local IP that the other machines can reach to.
+ # You can also set bind to '0.0.0.0' which listen in all interfaces.
+ # If you really need to bind to an external accessible IP, make
+ # sure you add extra firewall rules to prevent unauthorized access.
+ redis['bind'] = '10.0.0.1'
- # Define a port so Redis can listen for TCP requests which will allow other
- # machines to connect to it.
- redis['port'] = 6379
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
- # Set up password authentication for Redis (use the same password in all nodes).
- redis['password'] = 'redis-password-goes-here'
- ```
+ # Set up password authentication for Redis (use the same password in all nodes).
+ redis['password'] = 'redis-password-goes-here'
+ ```
1. Only the primary GitLab application server should handle migrations. To
prevent database migrations from running on upgrade, add the following
configuration to your `/etc/gitlab/gitlab.rb` file:
- ```
- gitlab_rails['auto_migrate'] = false
- ```
+ ```
+ gitlab_rails['auto_migrate'] = false
+ ```
1. [Reconfigure Omnibus GitLab][reconfigure] for the changes to take effect.
@@ -402,42 +406,42 @@ The prerequisites for a HA Redis setup are the following:
1. SSH into the **slave** Redis server.
1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
package you want using **steps 1 and 2** from the GitLab downloads page.
- - Make sure you select the correct Omnibus package, with the same version
- and type (Community, Enterprise editions) of your current install.
- - Do not complete any other steps on the download page.
+ - Make sure you select the correct Omnibus package, with the same version
+ and type (Community, Enterprise editions) of your current install.
+ - Do not complete any other steps on the download page.
1. Edit `/etc/gitlab/gitlab.rb` and add the contents:
- ```ruby
- # Specify server role as 'redis_slave_role'
- roles ['redis_slave_role']
+ ```ruby
+ # Specify server role as 'redis_slave_role'
+ roles ['redis_slave_role']
- # IP address pointing to a local IP that the other machines can reach to.
- # You can also set bind to '0.0.0.0' which listen in all interfaces.
- # If you really need to bind to an external accessible IP, make
- # sure you add extra firewall rules to prevent unauthorized access.
- redis['bind'] = '10.0.0.2'
+ # IP address pointing to a local IP that the other machines can reach to.
+ # You can also set bind to '0.0.0.0' which listen in all interfaces.
+ # If you really need to bind to an external accessible IP, make
+ # sure you add extra firewall rules to prevent unauthorized access.
+ redis['bind'] = '10.0.0.2'
- # Define a port so Redis can listen for TCP requests which will allow other
- # machines to connect to it.
- redis['port'] = 6379
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
- # The same password for Redis authentication you set up for the master node.
- redis['password'] = 'redis-password-goes-here'
+ # The same password for Redis authentication you set up for the master node.
+ redis['password'] = 'redis-password-goes-here'
- # The IP of the master Redis node.
- redis['master_ip'] = '10.0.0.1'
+ # The IP of the master Redis node.
+ redis['master_ip'] = '10.0.0.1'
- # Port of master Redis server, uncomment to change to non default. Defaults
- # to `6379`.
- #redis['master_port'] = 6379
- ```
+ # Port of master Redis server, uncomment to change to non default. Defaults
+ # to `6379`.
+ #redis['master_port'] = 6379
+ ```
1. To prevent reconfigure from running automatically on upgrade, run:
- ```
- sudo touch /etc/gitlab/skip-auto-reconfigure
- ```
+ ```
+ sudo touch /etc/gitlab/skip-auto-reconfigure
+ ```
1. [Reconfigure Omnibus GitLab][reconfigure] for the changes to take effect.
1. Go through the steps again for all the other slave nodes.
@@ -487,89 +491,89 @@ multiple machines with the Sentinel daemon.
1. **You can omit this step if the Sentinels will be hosted in the same node as
the other Redis instances.**
- [Download/install](https://about.gitlab.com/downloads-ee) the
- Omnibus GitLab Enterprise Edition package using **steps 1 and 2** from the
- GitLab downloads page.
- - Make sure you select the correct Omnibus package, with the same version
- the GitLab application is running.
- - Do not complete any other steps on the download page.
+ [Download/install](https://about.gitlab.com/downloads-ee) the
+ Omnibus GitLab Enterprise Edition package using **steps 1 and 2** from the
+ GitLab downloads page.
+ - Make sure you select the correct Omnibus package, with the same version
+ the GitLab application is running.
+ - Do not complete any other steps on the download page.
1. Edit `/etc/gitlab/gitlab.rb` and add the contents (if you are installing the
Sentinels in the same node as the other Redis instances, some values might
be duplicate below):
- ```ruby
- roles ['redis_sentinel_role']
-
- # Must be the same in every sentinel node
- redis['master_name'] = 'gitlab-redis'
-
- # The same password for Redis authentication you set up for the master node.
- redis['master_password'] = 'redis-password-goes-here'
-
- # The IP of the master Redis node.
- redis['master_ip'] = '10.0.0.1'
-
- # Define a port so Redis can listen for TCP requests which will allow other
- # machines to connect to it.
- redis['port'] = 6379
-
- # Port of master Redis server, uncomment to change to non default. Defaults
- # to `6379`.
- #redis['master_port'] = 6379
-
- ## Configure Sentinel
- sentinel['bind'] = '10.0.0.1'
-
- # Port that Sentinel listens on, uncomment to change to non default. Defaults
- # to `26379`.
- # sentinel['port'] = 26379
-
- ## Quorum must reflect the amount of voting sentinels it take to start a failover.
- ## Value must NOT be greater then the amount of sentinels.
- ##
- ## The quorum can be used to tune Sentinel in two ways:
- ## 1. If a the quorum is set to a value smaller than the majority of Sentinels
- ## we deploy, we are basically making Sentinel more sensible to master failures,
- ## triggering a failover as soon as even just a minority of Sentinels is no longer
- ## able to talk with the master.
- ## 1. If a quorum is set to a value greater than the majority of Sentinels, we are
- ## making Sentinel able to failover only when there are a very large number (larger
- ## than majority) of well connected Sentinels which agree about the master being down.s
- sentinel['quorum'] = 2
-
- ## Consider unresponsive server down after x amount of ms.
- # sentinel['down_after_milliseconds'] = 10000
-
- ## Specifies the failover timeout in milliseconds. It is used in many ways:
- ##
- ## - The time needed to re-start a failover after a previous failover was
- ## already tried against the same master by a given Sentinel, is two
- ## times the failover timeout.
- ##
- ## - The time needed for a slave replicating to a wrong master according
- ## to a Sentinel current configuration, to be forced to replicate
- ## with the right master, is exactly the failover timeout (counting since
- ## the moment a Sentinel detected the misconfiguration).
- ##
- ## - The time needed to cancel a failover that is already in progress but
- ## did not produced any configuration change (SLAVEOF NO ONE yet not
- ## acknowledged by the promoted slave).
- ##
- ## - The maximum time a failover in progress waits for all the slaves to be
- ## reconfigured as slaves of the new master. However even after this time
- ## the slaves will be reconfigured by the Sentinels anyway, but not with
- ## the exact parallel-syncs progression as specified.
- # sentinel['failover_timeout'] = 60000
- ```
+ ```ruby
+ roles ['redis_sentinel_role']
+
+ # Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ # The same password for Redis authentication you set up for the master node.
+ redis['master_password'] = 'redis-password-goes-here'
+
+ # The IP of the master Redis node.
+ redis['master_ip'] = '10.0.0.1'
+
+ # Define a port so Redis can listen for TCP requests which will allow other
+ # machines to connect to it.
+ redis['port'] = 6379
+
+ # Port of master Redis server, uncomment to change to non default. Defaults
+ # to `6379`.
+ #redis['master_port'] = 6379
+
+ ## Configure Sentinel
+ sentinel['bind'] = '10.0.0.1'
+
+ # Port that Sentinel listens on, uncomment to change to non default. Defaults
+ # to `26379`.
+ # sentinel['port'] = 26379
+
+ ## Quorum must reflect the amount of voting sentinels it take to start a failover.
+ ## Value must NOT be greater then the amount of sentinels.
+ ##
+ ## The quorum can be used to tune Sentinel in two ways:
+ ## 1. If a the quorum is set to a value smaller than the majority of Sentinels
+ ## we deploy, we are basically making Sentinel more sensible to master failures,
+ ## triggering a failover as soon as even just a minority of Sentinels is no longer
+ ## able to talk with the master.
+ ## 1. If a quorum is set to a value greater than the majority of Sentinels, we are
+ ## making Sentinel able to failover only when there are a very large number (larger
+ ## than majority) of well connected Sentinels which agree about the master being down.s
+ sentinel['quorum'] = 2
+
+ ## Consider unresponsive server down after x amount of ms.
+ # sentinel['down_after_milliseconds'] = 10000
+
+ ## Specifies the failover timeout in milliseconds. It is used in many ways:
+ ##
+ ## - The time needed to re-start a failover after a previous failover was
+ ## already tried against the same master by a given Sentinel, is two
+ ## times the failover timeout.
+ ##
+ ## - The time needed for a slave replicating to a wrong master according
+ ## to a Sentinel current configuration, to be forced to replicate
+ ## with the right master, is exactly the failover timeout (counting since
+ ## the moment a Sentinel detected the misconfiguration).
+ ##
+ ## - The time needed to cancel a failover that is already in progress but
+ ## did not produced any configuration change (SLAVEOF NO ONE yet not
+ ## acknowledged by the promoted slave).
+ ##
+ ## - The maximum time a failover in progress waits for all the slaves to be
+ ## reconfigured as slaves of the new master. However even after this time
+ ## the slaves will be reconfigured by the Sentinels anyway, but not with
+ ## the exact parallel-syncs progression as specified.
+ # sentinel['failover_timeout'] = 60000
+ ```
1. To prevent database migrations from running on upgrade, run:
- ```
- sudo touch /etc/gitlab/skip-auto-reconfigure
- ```
+ ```
+ sudo touch /etc/gitlab/skip-auto-reconfigure
+ ```
- Only the primary GitLab application server should handle migrations.
+ Only the primary GitLab application server should handle migrations.
1. [Reconfigure Omnibus GitLab][reconfigure] for the changes to take effect.
1. Go through the steps again for all the other Sentinel nodes.
@@ -593,20 +597,20 @@ which ideally should not have Redis or Sentinels on it for a HA setup.
1. SSH into the server where the GitLab application is installed.
1. Edit `/etc/gitlab/gitlab.rb` and add/change the following lines:
- ```
- ## Must be the same in every sentinel node
- redis['master_name'] = 'gitlab-redis'
-
- ## The same password for Redis authentication you set up for the master node.
- redis['master_password'] = 'redis-password-goes-here'
-
- ## A list of sentinels with `host` and `port`
- gitlab_rails['redis_sentinels'] = [
- {'host' => '10.0.0.1', 'port' => 26379},
- {'host' => '10.0.0.2', 'port' => 26379},
- {'host' => '10.0.0.3', 'port' => 26379}
- ]
- ```
+ ```ruby
+ ## Must be the same in every sentinel node
+ redis['master_name'] = 'gitlab-redis'
+
+ ## The same password for Redis authentication you set up for the master node.
+ redis['master_password'] = 'redis-password-goes-here'
+
+ ## A list of sentinels with `host` and `port`
+ gitlab_rails['redis_sentinels'] = [
+ {'host' => '10.0.0.1', 'port' => 26379},
+ {'host' => '10.0.0.2', 'port' => 26379},
+ {'host' => '10.0.0.3', 'port' => 26379}
+ ]
+ ```
1. [Reconfigure Omnibus GitLab][reconfigure] for the changes to take effect.
@@ -791,31 +795,34 @@ cache, queues, and shared_state. To make this work with Sentinel:
1. Set the appropriate variable in `/etc/gitlab/gitlab.rb` for each instance you are using:
- ```ruby
- gitlab_rails['redis_cache_instance'] = REDIS_CACHE_URL
- gitlab_rails['redis_queues_instance'] = REDIS_QUEUES_URL
- gitlab_rails['redis_shared_state_instance'] = REDIS_SHARED_STATE_URL
- ```
+ ```ruby
+ gitlab_rails['redis_cache_instance'] = REDIS_CACHE_URL
+ gitlab_rails['redis_queues_instance'] = REDIS_QUEUES_URL
+ gitlab_rails['redis_shared_state_instance'] = REDIS_SHARED_STATE_URL
+ ```
+
**Note**: Redis URLs should be in the format: `redis://:PASSWORD@SENTINEL_MASTER_NAME`
- 1. PASSWORD is the plaintext password for the Redis instance
- 1. SENTINEL_MASTER_NAME is the Sentinel master name (e.g. `gitlab-redis-cache`)
+ 1. PASSWORD is the plaintext password for the Redis instance
+ 1. SENTINEL_MASTER_NAME is the Sentinel master name (e.g. `gitlab-redis-cache`)
+
1. Include an array of hashes with host/port combinations, such as the following:
- ```ruby
- gitlab_rails['redis_cache_sentinels'] = [
- { host: REDIS_CACHE_SENTINEL_HOST, port: PORT1 },
- { host: REDIS_CACHE_SENTINEL_HOST2, port: PORT2 }
- ]
- gitlab_rails['redis_queues_sentinels'] = [
- { host: REDIS_QUEUES_SENTINEL_HOST, port: PORT1 },
- { host: REDIS_QUEUES_SENTINEL_HOST2, port: PORT2 }
- ]
- gitlab_rails['redis_shared_state_sentinels'] = [
- { host: SHARED_STATE_SENTINEL_HOST, port: PORT1 },
- { host: SHARED_STATE_SENTINEL_HOST2, port: PORT2 }
- ]
- ```
+ ```ruby
+ gitlab_rails['redis_cache_sentinels'] = [
+ { host: REDIS_CACHE_SENTINEL_HOST, port: PORT1 },
+ { host: REDIS_CACHE_SENTINEL_HOST2, port: PORT2 }
+ ]
+ gitlab_rails['redis_queues_sentinels'] = [
+ { host: REDIS_QUEUES_SENTINEL_HOST, port: PORT1 },
+ { host: REDIS_QUEUES_SENTINEL_HOST2, port: PORT2 }
+ ]
+ gitlab_rails['redis_shared_state_sentinels'] = [
+ { host: SHARED_STATE_SENTINEL_HOST, port: PORT1 },
+ { host: SHARED_STATE_SENTINEL_HOST2, port: PORT2 }
+ ]
+ ```
+
1. Note that for each persistence class, GitLab will default to using the
configuration specified in `gitlab_rails['redis_sentinels']` unless
overridden by the settings above.
@@ -879,12 +886,12 @@ in order for the HA setup to work as expected.
Before proceeding with the troubleshooting below, check your firewall rules:
- Redis machines
- - Accept TCP connection in `6379`
- - Connect to the other Redis machines via TCP in `6379`
+ - Accept TCP connection in `6379`
+ - Connect to the other Redis machines via TCP in `6379`
- Sentinel machines
- - Accept TCP connection in `26379`
- - Connect to other Sentinel machines via TCP in `26379`
- - Connect to the Redis machines via TCP in `6379`
+ - Accept TCP connection in `26379`
+ - Connect to other Sentinel machines via TCP in `26379`
+ - Connect to the Redis machines via TCP in `6379`
### Troubleshooting Redis replication
@@ -952,38 +959,38 @@ To make sure your configuration is correct:
1. SSH into your GitLab application server
1. Enter the Rails console:
- ```
- # For Omnibus installations
- sudo gitlab-rails console
+ ```
+ # For Omnibus installations
+ sudo gitlab-rails console
- # For source installations
- sudo -u git rails console production
- ```
+ # For source installations
+ sudo -u git rails console production
+ ```
1. Run in the console:
- ```ruby
- redis = Redis.new(Gitlab::Redis::SharedState.params)
- redis.info
- ```
+ ```ruby
+ redis = Redis.new(Gitlab::Redis::SharedState.params)
+ redis.info
+ ```
- Keep this screen open and try to simulate a failover below.
+ Keep this screen open and try to simulate a failover below.
1. To simulate a failover on master Redis, SSH into the Redis server and run:
- ```bash
- # port must match your master redis port, and the sleep time must be a few seconds bigger than defined one
- redis-cli -h localhost -p 6379 DEBUG sleep 20
- ```
+ ```bash
+ # port must match your master redis port, and the sleep time must be a few seconds bigger than defined one
+ redis-cli -h localhost -p 6379 DEBUG sleep 20
+ ```
1. Then back in the Rails console from the first step, run:
- ```
- redis.info
- ```
+ ```
+ redis.info
+ ```
- You should see a different port after a few seconds delay
- (the failover/reconnect time).
+ You should see a different port after a few seconds delay
+ (the failover/reconnect time).
## Changelog
diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md
index be6b547372a..63915e5d96c 100644
--- a/doc/administration/high_availability/redis_source.md
+++ b/doc/administration/high_availability/redis_source.md
@@ -1,3 +1,7 @@
+---
+type: reference
+---
+
# Configuring non-Omnibus Redis for GitLab HA
This is the documentation for configuring a Highly Available Redis setup when
@@ -49,22 +53,22 @@ Assuming that the Redis master instance IP is `10.0.0.1`:
1. [Install Redis](../../install/installation.md#7-redis).
1. Edit `/etc/redis/redis.conf`:
- ```conf
- ## Define a `bind` address pointing to a local IP that your other machines
- ## can reach you. If you really need to bind to an external accessible IP, make
- ## sure you add extra firewall rules to prevent unauthorized access:
- bind 10.0.0.1
+ ```conf
+ ## Define a `bind` address pointing to a local IP that your other machines
+ ## can reach you. If you really need to bind to an external accessible IP, make
+ ## sure you add extra firewall rules to prevent unauthorized access:
+ bind 10.0.0.1
- ## Define a `port` to force redis to listen on TCP so other machines can
- ## connect to it (default port is `6379`).
- port 6379
+ ## Define a `port` to force redis to listen on TCP so other machines can
+ ## connect to it (default port is `6379`).
+ port 6379
- ## Set up password authentication (use the same password in all nodes).
- ## The password should be defined equal for both `requirepass` and `masterauth`
- ## when setting up Redis to use with Sentinel.
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
- ```
+ ## Set up password authentication (use the same password in all nodes).
+ ## The password should be defined equal for both `requirepass` and `masterauth`
+ ## when setting up Redis to use with Sentinel.
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+ ```
1. Restart the Redis service for the changes to take effect.
@@ -75,25 +79,25 @@ Assuming that the Redis slave instance IP is `10.0.0.2`:
1. [Install Redis](../../install/installation.md#7-redis).
1. Edit `/etc/redis/redis.conf`:
- ```conf
- ## Define a `bind` address pointing to a local IP that your other machines
- ## can reach you. If you really need to bind to an external accessible IP, make
- ## sure you add extra firewall rules to prevent unauthorized access:
- bind 10.0.0.2
+ ```conf
+ ## Define a `bind` address pointing to a local IP that your other machines
+ ## can reach you. If you really need to bind to an external accessible IP, make
+ ## sure you add extra firewall rules to prevent unauthorized access:
+ bind 10.0.0.2
- ## Define a `port` to force redis to listen on TCP so other machines can
- ## connect to it (default port is `6379`).
- port 6379
+ ## Define a `port` to force redis to listen on TCP so other machines can
+ ## connect to it (default port is `6379`).
+ port 6379
- ## Set up password authentication (use the same password in all nodes).
- ## The password should be defined equal for both `requirepass` and `masterauth`
- ## when setting up Redis to use with Sentinel.
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
+ ## Set up password authentication (use the same password in all nodes).
+ ## The password should be defined equal for both `requirepass` and `masterauth`
+ ## when setting up Redis to use with Sentinel.
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
- ## Define `slaveof` pointing to the Redis master instance with IP and port.
- slaveof 10.0.0.1 6379
- ```
+ ## Define `slaveof` pointing to the Redis master instance with IP and port.
+ slaveof 10.0.0.1 6379
+ ```
1. Restart the Redis service for the changes to take effect.
1. Go through the steps again for all the other slave nodes.
@@ -110,56 +114,57 @@ master with IP `10.0.0.1` (some settings might overlap with the master):
1. [Install Redis Sentinel](https://redis.io/topics/sentinel)
1. Edit `/etc/redis/sentinel.conf`:
- ```conf
- ## Define a `bind` address pointing to a local IP that your other machines
- ## can reach you. If you really need to bind to an external accessible IP, make
- ## sure you add extra firewall rules to prevent unauthorized access:
- bind 10.0.0.1
-
- ## Define a `port` to force Sentinel to listen on TCP so other machines can
- ## connect to it (default port is `6379`).
- port 26379
-
- ## Set up password authentication (use the same password in all nodes).
- ## The password should be defined equal for both `requirepass` and `masterauth`
- ## when setting up Redis to use with Sentinel.
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
-
- ## Define with `sentinel auth-pass` the same shared password you have
- ## defined for both Redis master and slaves instances.
- sentinel auth-pass gitlab-redis redis-password-goes-here
-
- ## Define with `sentinel monitor` the IP and port of the Redis
- ## master node, and the quorum required to start a failover.
- sentinel monitor gitlab-redis 10.0.0.1 6379 2
-
- ## Define with `sentinel down-after-milliseconds` the time in `ms`
- ## that an unresponsive server will be considered down.
- sentinel down-after-milliseconds gitlab-redis 10000
-
- ## Define a value for `sentinel failover_timeout` in `ms`. This has multiple
- ## meanings:
- ##
- ## * The time needed to re-start a failover after a previous failover was
- ## already tried against the same master by a given Sentinel, is two
- ## times the failover timeout.
- ##
- ## * The time needed for a slave replicating to a wrong master according
- ## to a Sentinel current configuration, to be forced to replicate
- ## with the right master, is exactly the failover timeout (counting since
- ## the moment a Sentinel detected the misconfiguration).
- ##
- ## * The time needed to cancel a failover that is already in progress but
- ## did not produced any configuration change (SLAVEOF NO ONE yet not
- ## acknowledged by the promoted slave).
- ##
- ## * The maximum time a failover in progress waits for all the slaves to be
- ## reconfigured as slaves of the new master. However even after this time
- ## the slaves will be reconfigured by the Sentinels anyway, but not with
- ## the exact parallel-syncs progression as specified.
- sentinel failover_timeout 30000
- ```
+ ```conf
+ ## Define a `bind` address pointing to a local IP that your other machines
+ ## can reach you. If you really need to bind to an external accessible IP, make
+ ## sure you add extra firewall rules to prevent unauthorized access:
+ bind 10.0.0.1
+
+ ## Define a `port` to force Sentinel to listen on TCP so other machines can
+ ## connect to it (default port is `6379`).
+ port 26379
+
+ ## Set up password authentication (use the same password in all nodes).
+ ## The password should be defined equal for both `requirepass` and `masterauth`
+ ## when setting up Redis to use with Sentinel.
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+
+ ## Define with `sentinel auth-pass` the same shared password you have
+ ## defined for both Redis master and slaves instances.
+ sentinel auth-pass gitlab-redis redis-password-goes-here
+
+ ## Define with `sentinel monitor` the IP and port of the Redis
+ ## master node, and the quorum required to start a failover.
+ sentinel monitor gitlab-redis 10.0.0.1 6379 2
+
+ ## Define with `sentinel down-after-milliseconds` the time in `ms`
+ ## that an unresponsive server will be considered down.
+ sentinel down-after-milliseconds gitlab-redis 10000
+
+ ## Define a value for `sentinel failover_timeout` in `ms`. This has multiple
+ ## meanings:
+ ##
+ ## * The time needed to re-start a failover after a previous failover was
+ ## already tried against the same master by a given Sentinel, is two
+ ## times the failover timeout.
+ ##
+ ## * The time needed for a slave replicating to a wrong master according
+ ## to a Sentinel current configuration, to be forced to replicate
+ ## with the right master, is exactly the failover timeout (counting since
+ ## the moment a Sentinel detected the misconfiguration).
+ ##
+ ## * The time needed to cancel a failover that is already in progress but
+ ## did not produced any configuration change (SLAVEOF NO ONE yet not
+ ## acknowledged by the promoted slave).
+ ##
+ ## * The maximum time a failover in progress waits for all the slaves to be
+ ## reconfigured as slaves of the new master. However even after this time
+ ## the slaves will be reconfigured by the Sentinels anyway, but not with
+ ## the exact parallel-syncs progression as specified.
+ sentinel failover_timeout 30000
+ ```
+
1. Restart the Redis service for the changes to take effect.
1. Go through the steps again for all the other Sentinel nodes.
@@ -180,21 +185,21 @@ setup:
[resque.yml.example][resque], and uncomment the Sentinel lines, pointing to
the correct server credentials:
- ```yaml
- # resque.yaml
- production:
- url: redis://:redi-password-goes-here@gitlab-redis/
- sentinels:
- -
- host: 10.0.0.1
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.2
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.3
- port: 26379 # point to sentinel, not to redis port
- ```
+ ```yaml
+ # resque.yaml
+ production:
+ url: redis://:redi-password-goes-here@gitlab-redis/
+ sentinels:
+ -
+ host: 10.0.0.1
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.2
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.3
+ port: 26379 # point to sentinel, not to redis port
+ ```
1. [Restart GitLab][restart] for the changes to take effect.
@@ -232,23 +237,23 @@ or a failover promotes a different **Master** node.
1. In `/etc/redis/redis.conf`:
- ```conf
- bind 10.0.0.1
- port 6379
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
- ```
+ ```conf
+ bind 10.0.0.1
+ port 6379
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+ ```
1. In `/etc/redis/sentinel.conf`:
- ```conf
- bind 10.0.0.1
- port 26379
- sentinel auth-pass gitlab-redis redis-password-goes-here
- sentinel monitor gitlab-redis 10.0.0.1 6379 2
- sentinel down-after-milliseconds gitlab-redis 10000
- sentinel failover_timeout 30000
- ```
+ ```conf
+ bind 10.0.0.1
+ port 26379
+ sentinel auth-pass gitlab-redis redis-password-goes-here
+ sentinel monitor gitlab-redis 10.0.0.1 6379 2
+ sentinel down-after-milliseconds gitlab-redis 10000
+ sentinel failover_timeout 30000
+ ```
1. Restart the Redis service for the changes to take effect.
@@ -256,24 +261,24 @@ or a failover promotes a different **Master** node.
1. In `/etc/redis/redis.conf`:
- ```conf
- bind 10.0.0.2
- port 6379
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
- slaveof 10.0.0.1 6379
- ```
+ ```conf
+ bind 10.0.0.2
+ port 6379
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+ slaveof 10.0.0.1 6379
+ ```
1. In `/etc/redis/sentinel.conf`:
- ```conf
- bind 10.0.0.2
- port 26379
- sentinel auth-pass gitlab-redis redis-password-goes-here
- sentinel monitor gitlab-redis 10.0.0.1 6379 2
- sentinel down-after-milliseconds gitlab-redis 10000
- sentinel failover_timeout 30000
- ```
+ ```conf
+ bind 10.0.0.2
+ port 26379
+ sentinel auth-pass gitlab-redis redis-password-goes-here
+ sentinel monitor gitlab-redis 10.0.0.1 6379 2
+ sentinel down-after-milliseconds gitlab-redis 10000
+ sentinel failover_timeout 30000
+ ```
1. Restart the Redis service for the changes to take effect.
@@ -281,24 +286,24 @@ or a failover promotes a different **Master** node.
1. In `/etc/redis/redis.conf`:
- ```conf
- bind 10.0.0.3
- port 6379
- requirepass redis-password-goes-here
- masterauth redis-password-goes-here
- slaveof 10.0.0.1 6379
- ```
+ ```conf
+ bind 10.0.0.3
+ port 6379
+ requirepass redis-password-goes-here
+ masterauth redis-password-goes-here
+ slaveof 10.0.0.1 6379
+ ```
1. In `/etc/redis/sentinel.conf`:
- ```conf
- bind 10.0.0.3
- port 26379
- sentinel auth-pass gitlab-redis redis-password-goes-here
- sentinel monitor gitlab-redis 10.0.0.1 6379 2
- sentinel down-after-milliseconds gitlab-redis 10000
- sentinel failover_timeout 30000
- ```
+ ```conf
+ bind 10.0.0.3
+ port 26379
+ sentinel auth-pass gitlab-redis redis-password-goes-here
+ sentinel monitor gitlab-redis 10.0.0.1 6379 2
+ sentinel down-after-milliseconds gitlab-redis 10000
+ sentinel failover_timeout 30000
+ ```
1. Restart the Redis service for the changes to take effect.
@@ -306,20 +311,20 @@ or a failover promotes a different **Master** node.
1. Edit `/home/git/gitlab/config/resque.yml`:
- ```yaml
- production:
- url: redis://:redi-password-goes-here@gitlab-redis/
- sentinels:
- -
- host: 10.0.0.1
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.2
- port: 26379 # point to sentinel, not to redis port
- -
- host: 10.0.0.3
- port: 26379 # point to sentinel, not to redis port
- ```
+ ```yaml
+ production:
+ url: redis://:redi-password-goes-here@gitlab-redis/
+ sentinels:
+ -
+ host: 10.0.0.1
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.2
+ port: 26379 # point to sentinel, not to redis port
+ -
+ host: 10.0.0.3
+ port: 26379 # point to sentinel, not to redis port
+ ```
1. [Restart GitLab][restart] for the changes to take effect.
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index 1b01419e062..abe39d188aa 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -2,7 +2,6 @@
> [Introduced][ce-2371] in GitLab 8.4.
----
## Automatic housekeeping
GitLab automatically runs `git gc` and `git repack` on repositories
@@ -32,8 +31,6 @@ the `pushes_since_gc` value is 200 a `git gc` will be run.
You can find this option under your project's **Settings > General > Advanced**.
----
-
![Housekeeping settings](img/housekeeping_settings.png)
[ce-2371]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2371 "Housekeeping merge request"
diff --git a/doc/administration/audit_log.png b/doc/administration/img/audit_log.png
index d4f4c2abf38..d4f4c2abf38 100644
--- a/doc/administration/audit_log.png
+++ b/doc/administration/img/audit_log.png
Binary files differ
diff --git a/doc/administration/auditor_access_form.png b/doc/administration/img/auditor_access_form.png
index c179a7d3b0a..c179a7d3b0a 100644
--- a/doc/administration/auditor_access_form.png
+++ b/doc/administration/img/auditor_access_form.png
Binary files differ
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index 8de7b0bc57e..c2ac063ce37 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -1,6 +1,7 @@
# PlantUML & GitLab
-> [Introduced][ce-8537] in GitLab 8.16.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8537) in
+> GitLab 8.16.
When [PlantUML](http://plantuml.com) integration is enabled and configured in
GitLab we are able to create simple diagrams in AsciiDoc and Markdown documents
@@ -15,7 +16,9 @@ server that will generate the diagrams.
With Docker, you can just run a container like this:
-`docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat`
+```sh
+docker run -d --name plantuml -p 8080:8080 plantuml/plantuml-server:tomcat
+```
The **PlantUML URL** will be the hostname of the server running the container.
@@ -26,7 +29,7 @@ own PlantUML server is easy in Debian/Ubuntu distributions using Tomcat.
First you need to create a `plantuml.war` file from the source code:
-```
+```sh
sudo apt-get install graphviz openjdk-8-jdk git-core maven
git clone https://github.com/plantuml/plantuml-server.git
cd plantuml-server
@@ -36,7 +39,7 @@ mvn package
The above sequence of commands will generate a WAR file that can be deployed
using Tomcat:
-```
+```sh
sudo apt-get install tomcat7
sudo cp target/plantuml.war /var/lib/tomcat7/webapps/plantuml.war
sudo chown tomcat7:tomcat7 /var/lib/tomcat7/webapps/plantuml.war
@@ -46,7 +49,7 @@ sudo service tomcat7 restart
Once the Tomcat service restarts the PlantUML service will be ready and
listening for requests on port 8080:
-```
+```text
http://localhost:8080/plantuml
```
@@ -57,9 +60,10 @@ you can change these defaults by editing the `/etc/tomcat7/server.xml` file.
You need to enable PlantUML integration from Settings under Admin Area. To do
that, login with an Admin account and do following:
- - in GitLab go to **Admin Area**->**Settings**->**Integrations**->**PlantUML**
- - check **Enable PlantUML** checkbox
- - set the PlantUML instance as **PlantUML URL**
+- In GitLab, go to **Admin Area > Settings > Integrations**.
+- Expand the **PlantUML** section.
+- Check **Enable PlantUML** checkbox.
+- Set the PlantUML instance as **PlantUML URL**.
## Creating Diagrams
@@ -68,33 +72,34 @@ our AsciiDoc snippets, wikis and repos using delimited blocks:
- **Markdown**
- <pre>
- ```plantuml
- Bob -> Alice : hello
- Alice -> Bob : Go Away
- ```</pre>
+ ````markdown
+ ```plantuml
+ Bob -> Alice : hello
+ Alice -> Bob : Go Away
+ ```
+ ````
- **AsciiDoc**
- ```
- [plantuml, format="png", id="myDiagram", width="200px"]
- ----
- Bob->Alice : hello
- Alice -> Bob : Go Away
- ----
- ```
+ ```text
+ [plantuml, format="png", id="myDiagram", width="200px"]
+ ----
+ Bob->Alice : hello
+ Alice -> Bob : Go Away
+ ----
+ ```
- **reStructuredText**
- ```
- .. plantuml::
- :caption: Caption with **bold** and *italic*
+ ```text
+ .. plantuml::
+ :caption: Caption with **bold** and *italic*
- Bob -> Alice: hello
- Alice -> Bob: Go Away
- ```
+ Bob -> Alice: hello
+ Alice -> Bob: Go Away
+ ```
- You can also use the `uml::` directive for compatibility with [sphinxcontrib-plantuml](https://pypi.org/project/sphinxcontrib-plantuml/), but please note that we currently only support the `caption` option.
+ You can also use the `uml::` directive for compatibility with [sphinxcontrib-plantuml](https://pypi.org/project/sphinxcontrib-plantuml/), but please note that we currently only support the `caption` option.
The above blocks will be converted to an HTML img tag with source pointing to the
PlantUML instance. If the PlantUML server is correctly configured, this should
@@ -111,12 +116,10 @@ diagram delimiters `@startuml`/`@enduml` as these are replaced by the AsciiDoc `
Some parameters can be added to the AsciiDoc block definition:
- - *format*: Can be either `png` or `svg`. Note that `svg` is not supported by
- all browsers so use with care. The default is `png`.
- - *id*: A CSS id added to the diagram HTML tag.
- - *width*: Width attribute added to the img tag.
- - *height*: Height attribute added to the img tag.
+- *format*: Can be either `png` or `svg`. Note that `svg` is not supported by
+ all browsers so use with care. The default is `png`.
+- *id*: A CSS id added to the diagram HTML tag.
+- *width*: Width attribute added to the img tag.
+- *height*: Height attribute added to the img tag.
Markdown does not support any parameters and will always use PNG format.
-
-[ce-8537]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8537
diff --git a/doc/administration/integration/terminal.md b/doc/administration/integration/terminal.md
index c34858cd0db..24c9cc0bea9 100644
--- a/doc/administration/integration/terminal.md
+++ b/doc/administration/integration/terminal.md
@@ -41,9 +41,9 @@ detail below.
- Every session URL that is created has an authorization header that needs to be sent, to establish a `wss` connection.
- The session URL is not exposed to the users in any way. GitLab holds all the state internally and proxies accordingly.
-## Enabling and disabling terminal support
+## Enabling and disabling terminal support
-NOTE: **Note:** AWS Elastic Load Balancers (ELBs) do not support web sockets.
+NOTE: **Note:** AWS Elastic Load Balancers (ELBs) do not support web sockets.
AWS Application Load Balancers (ALBs) must be used if you want web terminals
to work. See [AWS Elastic Load Balancing Product Comparison](https://aws.amazon.com/elasticloadbalancing/features/#compare)
for more information.
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 9df7b2526e2..2b624f8de77 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -13,8 +13,6 @@ if you want to know how to disable it.
To disable artifacts site-wide, follow the steps below.
----
-
**In Omnibus installations:**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
@@ -25,8 +23,6 @@ To disable artifacts site-wide, follow the steps below.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**In installations from source:**
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
@@ -49,8 +45,6 @@ this is done when the job succeeds, but can also be done on failure, or always,
To change the location where the artifacts are stored locally, follow the steps
below.
----
-
**In Omnibus installations:**
_The artifacts are stored by default in
@@ -65,8 +59,6 @@ _The artifacts are stored by default in
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**In installations from source:**
_The artifacts are stored by default in
@@ -123,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) |
@@ -167,8 +160,6 @@ _The artifacts are stored by default in
gitlab-rake gitlab:artifacts:migrate
```
----
-
**In installations from source:**
_The artifacts are stored by default in
@@ -207,8 +198,6 @@ right after that date passes. Artifacts are cleaned up by the
To change the default schedule on which the artifacts are expired, follow the
steps below.
----
-
**In Omnibus installations:**
1. Edit `/etc/gitlab/gitlab.rb` and comment out or add the following line
@@ -219,8 +208,6 @@ steps below.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**In installations from source:**
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
@@ -240,8 +227,6 @@ steps below.
To disable [the dependencies validation](../ci/yaml/README.md#when-a-dependent-job-will-fail),
you can flip the feature flag from a Rails console.
----
-
**In Omnibus installations:**
1. Enter the Rails console:
@@ -256,8 +241,6 @@ you can flip the feature flag from a Rails console.
Feature.enable('ci_disable_validates_dependencies')
```
----
-
**In installations from source:**
1. Enter the Rails console:
diff --git a/doc/administration/job_traces.md b/doc/administration/job_traces.md
index 957916893d7..6a06eb240de 100644
--- a/doc/administration/job_traces.md
+++ b/doc/administration/job_traces.md
@@ -95,9 +95,9 @@ If job traces have already been archived into local storage, and you want to mig
1. Ensure [Object storage integration for Job Artifacts](job_artifacts.md#object-storage-settings) is enabled
1. Execute the following command
- ```bash
- gitlab-rake gitlab:traces:migrate
- ```
+ ```bash
+ gitlab-rake gitlab:traces:migrate
+ ```
## How to remove job traces
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 5a2f389d298..44a33eb7bcf 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -157,16 +157,22 @@ This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/githost.log` for
installations from source.
+NOTE: **Note:**
+After 12.2, this file will be 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`
@@ -277,9 +283,6 @@ 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`
Introduced in GitLab 12.0. This file lives in `/var/log/gitlab/gitlab-rails/auth.log` for
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/ip_whitelist.md b/doc/administration/monitoring/ip_whitelist.md
index ad2773de132..6bb2fe81b2c 100644
--- a/doc/administration/monitoring/ip_whitelist.md
+++ b/doc/administration/monitoring/ip_whitelist.md
@@ -12,9 +12,9 @@ hosts or use IP ranges:
1. Open `/etc/gitlab/gitlab.rb` and add or uncomment the following:
- ```ruby
- gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
- ```
+ ```ruby
+ gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
+ ```
1. Save the file and [reconfigure] GitLab for the changes to take effect.
@@ -24,13 +24,13 @@ hosts or use IP ranges:
1. Edit `config/gitlab.yml`:
- ```yaml
- monitoring:
- # by default only local IPs are allowed to access monitoring resources
- ip_whitelist:
- - 127.0.0.0/8
- - 192.168.0.1
- ```
+ ```yaml
+ monitoring:
+ # by default only local IPs are allowed to access monitoring resources
+ ip_whitelist:
+ - 127.0.0.0/8
+ - 192.168.0.1
+ ```
1. Save the file and [restart] GitLab for the changes to take effect.
diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md
index 771584268d9..9a25cc04ee7 100644
--- a/doc/administration/monitoring/performance/gitlab_configuration.md
+++ b/doc/administration/monitoring/performance/gitlab_configuration.md
@@ -8,12 +8,8 @@ The minimum required settings you need to set are the InfluxDB host and port.
Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the
changes.
----
-
![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png)
----
-
Finally, a restart of all GitLab processes is required for the changes to take
effect:
@@ -30,8 +26,6 @@ sudo service gitlab restart
When any migrations are pending, the metrics are disabled until the migrations
have been performed.
----
-
Read more on:
- [Introduction to GitLab Performance Monitoring](introduction.md)
diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png
index 2bf686f9017..8a6f8b3b273 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..265178729c4 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_redis_calls.png b/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png
new file mode 100644
index 00000000000..78dd7594adf
--- /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..f4068268137
--- /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..dab323eb066 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..3b34f207974 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 95f497a1470..15eab7dcab0 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -8,15 +8,16 @@ 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)
+- 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)
- 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 Redis calls, click through for details of these calls
+ ![Redis profiling using the Performance Bar](img/performance_bar_redis_calls.png)
- time taken and number of Ruby GC calls
On the far right is a request selector that allows you to view the same metrics
@@ -40,10 +41,7 @@ display it.
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..9f671e0db11 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..054fa547704 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 |
diff --git a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
index f68b03d1ade..9aa4dfa5ab7 100644
--- a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
+++ b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
@@ -12,9 +12,9 @@ To enable the GitLab monitor exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- gitlab_monitor['enable'] = true
- ```
+ ```ruby
+ gitlab_monitor['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index ce65d77274b..c8968c51393 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -39,9 +39,9 @@ To disable Prometheus and all of its exporters, as well as any added in the futu
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `false`:
- ```ruby
- prometheus_monitoring['enable'] = false
- ```
+ ```ruby
+ prometheus_monitoring['enable'] = false
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect.
@@ -61,19 +61,19 @@ To change the address/port that Prometheus listens on:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line:
- ```ruby
- prometheus['listen_address'] = 'localhost:9090'
- ```
+ ```ruby
+ prometheus['listen_address'] = 'localhost:9090'
+ ```
- Replace `localhost:9090` with the address/port you want Prometheus to
- listen on. If you would like to allow access to Prometheus to hosts other
- than `localhost`, leave out the host, or use `0.0.0.0` to allow public access:
+ Replace `localhost:9090` with the address/port you want Prometheus to
+ listen on. If you would like to allow access to Prometheus to hosts other
+ than `localhost`, leave out the host, or use `0.0.0.0` to allow public access:
- ```ruby
- prometheus['listen_address'] = ':9090'
- # or
- prometheus['listen_address'] = '0.0.0.0:9090'
- ```
+ ```ruby
+ prometheus['listen_address'] = ':9090'
+ # or
+ prometheus['listen_address'] = '0.0.0.0:9090'
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
@@ -90,22 +90,22 @@ To use an external Prometheus server:
1. Edit `/etc/gitlab/gitlab.rb`.
1. Disable the bundled Prometheus:
- ```ruby
- prometheus['enable'] = false
- ```
+ ```ruby
+ prometheus['enable'] = false
+ ```
1. Set each bundled service's [exporter](#bundled-software-metrics) to listen on a network address, for example:
- ```ruby
- gitlab_monitor['listen_address'] = '0.0.0.0'
- sidekiq['listen_address'] = '0.0.0.0'
- gitlab_monitor['listen_port'] = '9168'
- node_exporter['listen_address'] = '0.0.0.0:9100'
- redis_exporter['listen_address'] = '0.0.0.0:9121'
- postgres_exporter['listen_address'] = '0.0.0.0:9187'
- gitaly['prometheus_listen_addr'] = "0.0.0.0:9236"
- gitlab_workhorse['prometheus_listen_addr'] = "0.0.0.0:9229"
- ```
+ ```ruby
+ gitlab_monitor['listen_address'] = '0.0.0.0'
+ sidekiq['listen_address'] = '0.0.0.0'
+ gitlab_monitor['listen_port'] = '9168'
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+ postgres_exporter['listen_address'] = '0.0.0.0:9187'
+ gitaly['prometheus_listen_addr'] = "0.0.0.0:9236"
+ gitlab_workhorse['prometheus_listen_addr'] = "0.0.0.0:9229"
+ ```
1. Install and set up a dedicated Prometheus instance, if necessary, using the [official installation instructions](https://prometheus.io/docs/prometheus/latest/installation/).
1. Add the Prometheus server IP address to the [monitoring IP whitelist](../ip_whitelist.html). For example:
@@ -117,14 +117,14 @@ To use an external Prometheus server:
1. To scrape nginx metrics, you'll also need to configure nginx to allow the Prometheus server
IP. For example:
- ```ruby
- nginx['status']['options'] = {
- "server_tokens" => "off",
- "access_log" => "off",
- "allow" => "192.168.0.1",
- "deny" => "all",
- }
- ```
+ ```ruby
+ nginx['status']['options'] = {
+ "server_tokens" => "off",
+ "access_log" => "off",
+ "allow" => "192.168.0.1",
+ "deny" => "all",
+ }
+ ```
1. [Reconfigure GitLab][reconfigure] to apply the changes
1. Edit the Prometheus server's configuration file.
@@ -132,19 +132,59 @@ To use an external Prometheus server:
[scrape target configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Cscrape_config%3E).
For example, a sample snippet using `static_configs`:
- ```yaml
- scrape_configs:
- - job_name: 'gitlab_exporters'
- static_configs:
- - targets: ['1.1.1.1:9168', '1.1.1.1:9236', '1.1.1.1:9236', '1.1.1.1:9100', '1.1.1.1:9121', '1.1.1.1:9187']
-
- - job_name: 'gitlab_metrics'
- metrics_path: /-/metrics
- static_configs:
- - targets: ['1.1.1.1:443']
- ```
-
-1. Restart the Prometheus server.
+ ```yaml
+ scrape_configs:
+ - job_name: nginx
+ static_configs:
+ - targets:
+ - 1.1.1.1:8060
+ - job_name: redis
+ static_configs:
+ - targets:
+ - 1.1.1.1:9121
+ - job_name: postgres
+ static_configs:
+ - targets:
+ - 1.1.1.1:9187
+ - job_name: node
+ static_configs:
+ - targets:
+ - 1.1.1.1:9100
+ - job_name: gitlab-workhorse
+ static_configs:
+ - targets:
+ - 1.1.1.1:9229
+ - job_name: gitlab-rails
+ metrics_path: "/-/metrics"
+ static_configs:
+ - targets:
+ - 1.1.1.1:8080
+ - job_name: gitlab-sidekiq
+ static_configs:
+ - targets:
+ - 1.1.1.1:8082
+ - job_name: gitlab_monitor_database
+ metrics_path: "/database"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitlab_monitor_sidekiq
+ metrics_path: "/sidekiq"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitlab_monitor_process
+ metrics_path: "/process"
+ static_configs:
+ - targets:
+ - 1.1.1.1:9168
+ - job_name: gitaly
+ static_configs:
+ - targets:
+ - 1.1.1.1:9236
+ ```
+
+1. Reload the Prometheus server.
## Viewing performance metrics
@@ -241,9 +281,9 @@ To disable the monitoring of Kubernetes:
1. Edit `/etc/gitlab/gitlab.rb`.
1. Add or find and uncomment the following line and set it to `false`:
- ```ruby
- prometheus['monitor_kubernetes'] = false
- ```
+ ```ruby
+ prometheus['monitor_kubernetes'] = false
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect.
diff --git a/doc/administration/monitoring/prometheus/node_exporter.md b/doc/administration/monitoring/prometheus/node_exporter.md
index aef7758a88f..bcacfaa3be5 100644
--- a/doc/administration/monitoring/prometheus/node_exporter.md
+++ b/doc/administration/monitoring/prometheus/node_exporter.md
@@ -13,9 +13,9 @@ To enable the node exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- node_exporter['enable'] = true
- ```
+ ```ruby
+ node_exporter['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md
index d76834fdbea..6183594c69c 100644
--- a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md
+++ b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md
@@ -12,9 +12,9 @@ To enable the PgBouncer exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- pgbouncer_exporter['enable'] = true
- ```
+ ```ruby
+ pgbouncer_exporter['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect.
diff --git a/doc/administration/monitoring/prometheus/postgres_exporter.md b/doc/administration/monitoring/prometheus/postgres_exporter.md
index 8e2d3162f88..3ad15b65497 100644
--- a/doc/administration/monitoring/prometheus/postgres_exporter.md
+++ b/doc/administration/monitoring/prometheus/postgres_exporter.md
@@ -12,9 +12,9 @@ To enable the postgres exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- postgres_exporter['enable'] = true
- ```
+ ```ruby
+ postgres_exporter['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/monitoring/prometheus/redis_exporter.md b/doc/administration/monitoring/prometheus/redis_exporter.md
index d54d409dbb6..1520115a38d 100644
--- a/doc/administration/monitoring/prometheus/redis_exporter.md
+++ b/doc/administration/monitoring/prometheus/redis_exporter.md
@@ -13,9 +13,9 @@ To enable the Redis exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- redis_exporter['enable'] = true
- ```
+ ```ruby
+ redis_exporter['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index b329abdca08..ea69378b249 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -117,81 +117,81 @@ the database. The following instructions can be used to build OpenSSH 7.5:
1. First, download the package and install the required packages:
- ```
- sudo su -
- cd /tmp
- curl --remote-name https://mirrors.evowise.com/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
- tar xzvf openssh-7.5p1.tar.gz
- yum install rpm-build gcc make wget openssl-devel krb5-devel pam-devel libX11-devel xmkmf libXt-devel
- ```
+ ```
+ sudo su -
+ cd /tmp
+ curl --remote-name https://mirrors.evowise.com/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
+ tar xzvf openssh-7.5p1.tar.gz
+ yum install rpm-build gcc make wget openssl-devel krb5-devel pam-devel libX11-devel xmkmf libXt-devel
+ ```
1. Prepare the build by copying files to the right place:
- ```
- mkdir -p /root/rpmbuild/{SOURCES,SPECS}
- cp ./openssh-7.5p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/
- cp openssh-7.5p1.tar.gz /root/rpmbuild/SOURCES/
- cd /root/rpmbuild/SPECS
- ```
+ ```
+ mkdir -p /root/rpmbuild/{SOURCES,SPECS}
+ cp ./openssh-7.5p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/
+ cp openssh-7.5p1.tar.gz /root/rpmbuild/SOURCES/
+ cd /root/rpmbuild/SPECS
+ ```
1. Next, set the spec settings properly:
- ```
- sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
- sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
- sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
- ```
+ ```
+ sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
+ sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
+ sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
+ ```
1. Build the RPMs:
- ```
- rpmbuild -bb openssh.spec
- ```
+ ```
+ rpmbuild -bb openssh.spec
+ ```
1. Ensure the RPMs were built:
- ```
- ls -al /root/rpmbuild/RPMS/x86_64/
- ```
+ ```
+ ls -al /root/rpmbuild/RPMS/x86_64/
+ ```
- You should see something as the following:
+ You should see something as the following:
- ```
- total 1324
- drwxr-xr-x. 2 root root 4096 Jun 20 19:37 .
- drwxr-xr-x. 3 root root 19 Jun 20 19:37 ..
- -rw-r--r--. 1 root root 470828 Jun 20 19:37 openssh-7.5p1-1.x86_64.rpm
- -rw-r--r--. 1 root root 490716 Jun 20 19:37 openssh-clients-7.5p1-1.x86_64.rpm
- -rw-r--r--. 1 root root 17020 Jun 20 19:37 openssh-debuginfo-7.5p1-1.x86_64.rpm
- -rw-r--r--. 1 root root 367516 Jun 20 19:37 openssh-server-7.5p1-1.x86_64.rpm
- ```
+ ```
+ total 1324
+ drwxr-xr-x. 2 root root 4096 Jun 20 19:37 .
+ drwxr-xr-x. 3 root root 19 Jun 20 19:37 ..
+ -rw-r--r--. 1 root root 470828 Jun 20 19:37 openssh-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 490716 Jun 20 19:37 openssh-clients-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 17020 Jun 20 19:37 openssh-debuginfo-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 367516 Jun 20 19:37 openssh-server-7.5p1-1.x86_64.rpm
+ ```
1. Install the packages. OpenSSH packages will replace `/etc/pam.d/sshd`
with its own version, which may prevent users from logging in, so be sure
that the file is backed up and restored after installation:
- ```
- timestamp=$(date +%s)
- cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
- rpm -Uvh /root/rpmbuild/RPMS/x86_64/*.rpm
- yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
- ```
+ ```
+ timestamp=$(date +%s)
+ cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
+ rpm -Uvh /root/rpmbuild/RPMS/x86_64/*.rpm
+ yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
+ ```
1. Verify the installed version. In another window, attempt to login to the server:
- ```
- ssh -v <your-centos-machine>
- ```
+ ```
+ ssh -v <your-centos-machine>
+ ```
- You should see a line that reads: "debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5"
+ You should see a line that reads: "debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5"
- If not, you may need to restart sshd (e.g. `systemctl restart sshd.service`).
+ If not, you may need to restart sshd (e.g. `systemctl restart sshd.service`).
-1. *IMPORTANT!* Open a new SSH session to your server before exiting to make
- sure everything is working! If you need to downgrade, simple install the
- older package:
+1. *IMPORTANT!* Open a new SSH session to your server before exiting to make
+ sure everything is working! If you need to downgrade, simple install the
+ older package:
- ```
- # Only run this if you run into a problem logging in
- yum downgrade openssh-server openssh openssh-clients
- ```
+ ```
+ # Only run this if you run into a problem logging in
+ yum downgrade openssh-server openssh openssh-clients
+ ```
diff --git a/doc/administration/operations/filesystem_benchmarking.md b/doc/administration/operations/filesystem_benchmarking.md
index c0c242733a2..b5922d9d99d 100644
--- a/doc/administration/operations/filesystem_benchmarking.md
+++ b/doc/administration/operations/filesystem_benchmarking.md
@@ -78,34 +78,37 @@ executed, and then read the same 1,000 files.
[repository storage path](../repository_storage_paths.md).
1. Create a temporary directory for the test so it's easy to remove the files later:
- ```sh
- mkdir test; cd test
- ```
+ ```sh
+ mkdir test; cd test
+ ```
+
1. Run the command:
- ```sh
- time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
- ```
-1. To benchmark read performance, run the command:
+ ```sh
+ time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
+ ```
- ```sh
- time for i in {0..1000}; do cat "test${i}.txt" > /dev/null; done
- ```
-1. Remove the test files:
+1. To benchmark read performance, run the command:
```sh
- cd ../; rm -rf test
+ time for i in {0..1000}; do cat "test${i}.txt" > /dev/null; done
```
+1. Remove the test files:
+
+ ```sh
+ cd ../; rm -rf test
+ ```
+
The output of the `time for ...` commands will look similar to the following. The
important metric is the `real` time.
```sh
$ time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
-real 0m0.116s
-user 0m0.025s
-sys 0m0.091s
+real 0m0.116s
+user 0m0.025s
+sys 0m0.091s
$ time for i in {0..1000}; do cat "test${i}.txt" > /dev/null; done
diff --git a/doc/administration/pages/img/lets_encrypt_integration_v12_1.png b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png
new file mode 100644
index 00000000000..5ab63074e12
--- /dev/null
+++ b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png
Binary files differ
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 3a7ca517d56..774e7056845 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -4,21 +4,24 @@ description: 'Learn how to administer GitLab Pages.'
# GitLab Pages administration
-> **Notes:**
->
> - [Introduced][ee-80] in GitLab EE 8.3.
> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
-> - GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17.
-> - This guide is for Omnibus GitLab installations. If you have installed
-> GitLab from source, follow the [Pages source installation document](source.md).
-> - To learn how to use GitLab Pages, read the [user documentation][pages-userguide].
-> - Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
-
-This document describes how to set up the _latest_ GitLab Pages feature. Make
-sure to read the [changelog](#changelog) if you are upgrading to a new GitLab
+> - GitLab Pages [was ported][ce-14605] to Community Edition in GitLab 8.17.
+> - Support for subgroup project's websites was
+> [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
+
+GitLab Pages allows for hosting of static sites. It must be configured by an
+administrator. Separate [user documentation][pages-userguide] is available.
+
+Read the [changelog](#changelog) if you are upgrading to a new GitLab
version as it may include new features and changes needed to be made in your
configuration.
+NOTE: **Note:**
+This guide is for Omnibus GitLab installations. If you have installed
+GitLab from source, see
+[GitLab Pages administration for source installations](source.md).
+
## Overview
GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server
@@ -121,9 +124,9 @@ The Pages daemon doesn't listen to the outside world.
1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`:
- ```shell
- pages_external_url 'http://example.io'
- ```
+ ```shell
+ pages_external_url 'http://example.io'
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -146,16 +149,16 @@ outside world.
1. Place the certificate and key inside `/etc/gitlab/ssl`
1. In `/etc/gitlab/gitlab.rb` specify the following configuration:
- ```shell
- pages_external_url 'https://example.io'
+ ```shell
+ pages_external_url 'https://example.io'
- pages_nginx['redirect_http_to_https'] = true
- pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt"
- pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key"
- ```
+ pages_nginx['redirect_http_to_https'] = true
+ pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt"
+ pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key"
+ ```
- where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key,
- respectively.
+ where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key,
+ respectively.
1. [Reconfigure GitLab][reconfigure].
@@ -168,9 +171,9 @@ behavior:
1. Edit `/etc/gitlab/gitlab.rb`.
1. Set the `inplace_chroot` to `true` for GitLab Pages:
- ```shell
- gitlab_pages['inplace_chroot'] = true
- ```
+ ```shell
+ gitlab_pages['inplace_chroot'] = true
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -203,16 +206,16 @@ world. Custom domains are supported, but no TLS.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```shell
- pages_external_url "http://example.io"
- nginx['listen_addresses'] = ['192.0.2.1']
- pages_nginx['enable'] = false
- gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
- ```
+ ```shell
+ pages_external_url "http://example.io"
+ nginx['listen_addresses'] = ['192.0.2.1']
+ pages_nginx['enable'] = false
+ gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
+ ```
- where `192.0.2.1` is the primary IP address that GitLab is listening to and
- `192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
- listens on. If you don't have IPv6, you can omit the IPv6 address.
+ where `192.0.2.1` is the primary IP address that GitLab is listening to and
+ `192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
+ listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure].
@@ -234,26 +237,26 @@ world. Custom domains and TLS are supported.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```shell
- pages_external_url "https://example.io"
- nginx['listen_addresses'] = ['192.0.2.1']
- pages_nginx['enable'] = false
- gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
- gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
- gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
- gitlab_pages['external_https'] = ['192.0.2.2:443', '[2001::2]:443']
- ```
+ ```shell
+ pages_external_url "https://example.io"
+ nginx['listen_addresses'] = ['192.0.2.1']
+ pages_nginx['enable'] = false
+ gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
+ gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
+ gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
+ gitlab_pages['external_https'] = ['192.0.2.2:443', '[2001::2]:443']
+ ```
- where `192.0.2.1` is the primary IP address that GitLab is listening to and
- `192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
- listens on. If you don't have IPv6, you can omit the IPv6 address.
+ where `192.0.2.1` is the primary IP address that GitLab is listening to and
+ `192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
+ listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure].
### Custom domain verification
To prevent malicious users from hijacking domains that don't belong to them,
-GitLab supports [custom domain verification](../../user/project/pages/getting_started_part_three.md#dns-txt-record).
+GitLab supports [custom domain verification](../../user/project/pages/custom_domains_ssl_tls_certification/index.md#steps).
When adding a custom domain, users will be required to prove they own it by
adding a GitLab-controlled verification code to the DNS records for that domain.
@@ -262,6 +265,23 @@ verification requirement. Navigate to `Admin area ➔ Settings` and uncheck
**Require users to prove ownership of custom domains** in the Pages section.
This setting is enabled by default.
+### Let's Encrypt integration
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28996) in GitLab 12.1.
+
+[GitLab Pages' Let's Encrypt integration](../../user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md)
+allows users to add Let's Encrypt SSL certificates for GitLab Pages
+sites served under a custom domain.
+
+To enable it, you'll need to:
+
+1. Choose an email on which you will recieve notifications about expiring domains.
+1. Navigate to your instance's **Admin Area > Settings > Preferences** and expand **Pages** settings.
+1. Enter the email for receiving notifications and accept Let's Encrypt's Terms of Service as shown below.
+1. Click **Save changes**.
+
+![Let's Encrypt settings](img/lets_encrypt_integration_v12_1.png)
+
### Access control
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) in GitLab 11.5.
@@ -284,9 +304,9 @@ Pages access control is disabled by default. To enable it:
1. Enable it in `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_pages['access_control'] = true
- ```
+ ```ruby
+ gitlab_pages['access_control'] = true
+ ```
1. [Reconfigure GitLab][reconfigure].
1. Users can now configure it in their [projects' settings](../../user/project/pages/introduction.md#gitlab-pages-access-control-core-only).
@@ -299,9 +319,9 @@ pages:
1. Configure in `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_pages['http_proxy'] = 'http://example:8080'
- ```
+ ```ruby
+ gitlab_pages['http_proxy'] = 'http://example:8080'
+ ```
1. [Reconfigure Gitlab][reconfigure] for the changes to take effect.
@@ -316,9 +336,9 @@ Follow the steps below to configure verbose logging of GitLab Pages daemon.
If you wish to make it log events with level `DEBUG` you must configure this in
`/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_pages['log_verbose'] = true
- ```
+ ```shell
+ gitlab_pages['log_verbose'] = true
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -331,9 +351,9 @@ are stored.
If you wish to store them in another location you must set it up in
`/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_rails['pages_path'] = "/mnt/storage/pages"
- ```
+ ```shell
+ gitlab_rails['pages_path'] = "/mnt/storage/pages"
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -344,19 +364,19 @@ Omnibus GitLab 11.1.
1. By default the listener is configured to listen for requests on `localhost:8090`.
- If you wish to disable it you must configure this in
- `/etc/gitlab/gitlab.rb`:
+ If you wish to disable it you must configure this in
+ `/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_pages['listen_proxy'] = nil
- ```
+ ```shell
+ gitlab_pages['listen_proxy'] = nil
+ ```
- If you wish to make it listen on a different port you must configure this also in
- `/etc/gitlab/gitlab.rb`:
+ If you wish to make it listen on a different port you must configure this also in
+ `/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_pages['listen_proxy'] = "localhost:10080"
- ```
+ ```shell
+ gitlab_pages['listen_proxy'] = "localhost:10080"
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -378,28 +398,29 @@ Follow the steps below to configure GitLab Pages in a separate server.
1. On `app2` install GitLab omnibus and modify `/etc/gitlab/gitlab.rb` this way:
- ```shell
- external_url 'http://<ip-address-of-the-server>'
- pages_external_url "http://<your-pages-domain>"
- postgresql['enable'] = false
- redis['enable'] = false
- prometheus['enable'] = false
- unicorn['enable'] = false
- sidekiq['enable'] = false
- gitlab_workhorse['enable'] = false
- gitaly['enable'] = false
- alertmanager['enable'] = false
- node_exporter['enable'] = false
- gitlab_rails['auto_migrate'] = false
- ```
+ ```shell
+ external_url 'http://<ip-address-of-the-server>'
+ pages_external_url "http://<your-pages-domain>"
+ postgresql['enable'] = false
+ redis['enable'] = false
+ prometheus['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ gitaly['enable'] = false
+ alertmanager['enable'] = false
+ node_exporter['enable'] = false
+ gitlab_rails['auto_migrate'] = false
+ ```
+
1. Run `sudo gitlab-ctl reconfigure`.
1. On `app1` apply the following changes to `/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_pages['enable'] = false
- pages_external_url "http://<your-pages-domain>"
- gitlab_rails['pages_path'] = "/mnt/pages"
- ```
+ ```shell
+ gitlab_pages['enable'] = false
+ pages_external_url "http://<your-pages-domain>"
+ gitlab_rails['pages_path'] = "/mnt/pages"
+ ```
1. Run `sudo gitlab-ctl reconfigure`.
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index 2100f7cd707..b2cad6cf926 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -102,50 +102,50 @@ The Pages daemon doesn't listen to the outside world.
1. Install the Pages daemon:
- ```
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
- ```
+ ```
+ cd /home/git
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+ cd gitlab-pages
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+ sudo -u git -H make
+ ```
1. Go to the GitLab installation directory:
- ```bash
- cd /home/git/gitlab
- ```
+ ```bash
+ cd /home/git/gitlab
+ ```
1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and
the `host` to the FQDN under which GitLab Pages will be served:
- ```yaml
- ## GitLab Pages
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- # path: shared/pages
+ ```yaml
+ ## GitLab Pages
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
- host: example.io
- port: 80
- https: false
- ```
+ host: example.io
+ port: 80
+ https: false
+ ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
order to enable the pages daemon. In `gitlab_pages_options` the
`-pages-domain` must match the `host` setting that you set above.
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090"
- ```
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090"
+ ```
1. Copy the `gitlab-pages` Nginx configuration file:
- ```bash
- sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
- ```
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
+ ```
1. Restart NGINX
1. [Restart GitLab][restart]
@@ -165,27 +165,27 @@ outside world.
1. Install the Pages daemon:
- ```
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
- ```
+ ```
+ cd /home/git
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+ cd gitlab-pages
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+ sudo -u git -H make
+ ```
1. In `gitlab.yml`, set the port to `443` and https to `true`:
- ```bash
- ## GitLab Pages
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- # path: shared/pages
+ ```bash
+ ## GitLab Pages
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
- host: example.io
- port: 443
- https: true
- ```
+ host: example.io
+ port: 443
+ https: true
+ ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
order to enable the pages daemon. In `gitlab_pages_options` the
@@ -193,17 +193,17 @@ outside world.
The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
of the `example.io` domain:
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
- ```
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
+ ```
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
- ```bash
- sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
- ```
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
+ ```
1. Restart NGINX
1. [Restart GitLab][restart]
@@ -231,48 +231,48 @@ world. Custom domains are supported, but no TLS.
1. Install the Pages daemon:
- ```
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
- ```
+ ```
+ cd /home/git
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+ cd gitlab-pages
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+ sudo -u git -H make
+ ```
1. Edit `gitlab.yml` to look like the example below. You need to change the
`host` to the FQDN under which GitLab Pages will be served. Set
`external_http` to the secondary IP on which the pages daemon will listen
for connections:
- ```yaml
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- # path: shared/pages
+ ```yaml
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
- host: example.io
- port: 80
- https: false
+ host: example.io
+ port: 80
+ https: false
- external_http: 192.0.2.2:80
- ```
+ external_http: 192.0.2.2:80
+ ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
order to enable the pages daemon. In `gitlab_pages_options` the
`-pages-domain` and `-listen-http` must match the `host` and `external_http`
settings that you set above respectively:
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80"
- ```
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80"
+ ```
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
- ```bash
- sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
- ```
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
+ ```
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab
@@ -297,33 +297,33 @@ world. Custom domains and TLS are supported.
1. Install the Pages daemon:
- ```
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
- ```
+ ```
+ cd /home/git
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+ cd gitlab-pages
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+ sudo -u git -H make
+ ```
1. Edit `gitlab.yml` to look like the example below. You need to change the
`host` to the FQDN under which GitLab Pages will be served. Set
`external_http` and `external_https` to the secondary IP on which the pages
daemon will listen for connections:
- ```yaml
- ## GitLab Pages
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- # path: shared/pages
+ ```yaml
+ ## GitLab Pages
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
- host: example.io
- port: 443
- https: true
+ host: example.io
+ port: 443
+ https: true
- external_http: 192.0.2.2:80
- external_https: 192.0.2.2:443
- ```
+ external_http: 192.0.2.2:80
+ external_https: 192.0.2.2:443
+ ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
order to enable the pages daemon. In `gitlab_pages_options` the
@@ -332,17 +332,17 @@ world. Custom domains and TLS are supported.
The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
of the `example.io` domain:
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
- ```
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
+ ```
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
- ```bash
- sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
- ```
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
+ ```
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab
@@ -359,9 +359,9 @@ are stored.
If you wish to store them in another location you must set it up in
`/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['pages_path'] = "/mnt/storage/pages"
- ```
+ ```ruby
+ gitlab_rails['pages_path'] = "/mnt/storage/pages"
+ ```
1. [Reconfigure GitLab][reconfigure]
@@ -414,10 +414,10 @@ Pages access control is disabled by default. To enable it:
1. Modify your `config/gitlab.yml` file:
- ```yaml
- pages:
- access_control: true
- ```
+ ```yaml
+ pages:
+ access_control: true
+ ```
1. [Restart GitLab][restart].
1. Create a new [system OAuth application](../../integration/oauth_provider.md#adding-an-application-through-the-profile).
@@ -426,12 +426,12 @@ Pages access control is disabled by default. To enable it:
application, but it does need the "api" scope.
1. Start the Pages daemon with the following additional arguments:
- ```shell
- -auth-client-secret <OAuth code generated by GitLab> \
- -auth-redirect-uri http://projects.example.io/auth \
- -auth-secret <40 random hex characters> \
- -auth-server <URL of the GitLab instance>
- ```
+ ```shell
+ -auth-client-secret <OAuth code generated by GitLab> \
+ -auth-redirect-uri http://projects.example.io/auth \
+ -auth-secret <40 random hex characters> \
+ -auth-server <URL of the GitLab instance>
+ ```
1. Users can now configure it in their [projects' settings](../../user/project/pages/introduction.md#gitlab-pages-access-control-core-only).
@@ -444,12 +444,12 @@ are stored.
If you wish to store them in another location you must set it up in
`gitlab.yml` under the `pages` section:
- ```yaml
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- path: /mnt/storage/pages
- ```
+ ```yaml
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ path: /mnt/storage/pages
+ ```
1. [Restart GitLab][restart]
diff --git a/doc/administration/pseudonymizer.md b/doc/administration/pseudonymizer.md
index 78b2751da13..716a4259a64 100644
--- a/doc/administration/pseudonymizer.md
+++ b/doc/administration/pseudonymizer.md
@@ -65,8 +65,8 @@ To configure the pseudonymizer, you need to:
```yaml
pseudonymizer:
- manifest: config/pseudonymizer.yml
- upload:
+ manifest: config/pseudonymizer.yml
+ upload:
remote_directory: 'gitlab-elt' # bucket name
connection:
provider: AWS
diff --git a/doc/administration/raketasks/geo.md b/doc/administration/raketasks/geo.md
index 435aed8c413..691d34ab7fa 100644
--- a/doc/administration/raketasks/geo.md
+++ b/doc/administration/raketasks/geo.md
@@ -2,7 +2,7 @@
## Git housekeeping
-There are few tasks you can run to schedule a git housekeeping to start at the
+There are few tasks you can run to schedule a git housekeeping to start at the
next repository sync in a **Secondary node**:
### Incremental Repack
@@ -23,7 +23,7 @@ sudo -u git -H bundle exec rake geo:git:housekeeping:incremental_repack RAILS_EN
### Full Repack
-This is equivalent of running `git repack -d -A --pack-kept-objects` on a
+This is equivalent of running `git repack -d -A --pack-kept-objects` on a
_bare_ repository which will optionally, write a reachability bitmap index
when this is enabled in GitLab.
diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md
index 2b31233d429..8d0b5b42515 100644
--- a/doc/administration/raketasks/maintenance.md
+++ b/doc/administration/raketasks/maintenance.md
@@ -251,3 +251,22 @@ sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:*]
# to clear a lease for repository garbage collection in a specific project: (id=4)
sudo gitlab-rake gitlab:exclusive_lease:clear[project_housekeeping:4]
```
+
+## Display status of database migrations
+
+To check the status of migrations, you can use the following rake task:
+
+```bash
+sudo gitlab-rake db:migrate:status
+```
+
+This will output a table with a `Status` of `up` or `down` for
+each Migration ID.
+
+```bash
+database: gitlabhq_production
+
+ Status Migration ID Migration Name
+--------------------------------------------------
+ up migration_id migration_name
+``` \ No newline at end of file
diff --git a/doc/administration/raketasks/project_import_export.md b/doc/administration/raketasks/project_import_export.md
index 0599e12b913..138db4dfbb1 100644
--- a/doc/administration/raketasks/project_import_export.md
+++ b/doc/administration/raketasks/project_import_export.md
@@ -2,14 +2,14 @@
>**Note:**
>
-> - [Introduced][ce-3050] in GitLab 8.9.
-> - Importing will not be possible if the import instance version is lower
-> than that of the exporter.
-> - For existing installations, the project import option has to be enabled in
-> application settings (`/admin/application_settings`) under 'Import sources'.
-> - The exports are stored in a temporary [shared directory][tmp] and are deleted
-> every 24 hours by a specific worker.
-> - ImportExport can use object storage automatically starting from GitLab 11.3
+> - [Introduced][ce-3050] in GitLab 8.9.
+> - Importing will not be possible if the import instance version is lower
+> than that of the exporter.
+> - For existing installations, the project import option has to be enabled in
+> application settings (`/admin/application_settings`) under 'Import sources'.
+> - The exports are stored in a temporary [shared directory][tmp] and are deleted
+> every 24 hours by a specific worker.
+> - ImportExport can use object storage automatically starting from GitLab 11.3
The GitLab Import/Export version can be checked by using:
diff --git a/doc/administration/raketasks/storage.md b/doc/administration/raketasks/storage.md
index 42a1a1c2e60..2f83dd17d9f 100644
--- a/doc/administration/raketasks/storage.md
+++ b/doc/administration/raketasks/storage.md
@@ -1,17 +1,17 @@
# Repository Storage Rake Tasks
-This is a collection of rake tasks you can use to help you list and migrate
-existing projects and attachments associated with it from Legacy storage to
+This is a collection of rake tasks you can use to help you list and migrate
+existing projects and attachments associated with it from Legacy storage to
the new Hashed storage type.
You can read more about the storage types [here][storage-types].
## Migrate existing projects to Hashed storage
-Before migrating your existing projects, you should
+Before migrating your existing projects, you should
[enable hashed storage][storage-migration] for the new projects as well.
-This task will schedule all your existing projects and attachments associated with it to be migrated to the
+This task will schedule all your existing projects and attachments associated with it to be migrated to the
**Hashed** storage type:
**Omnibus Installation**
@@ -30,15 +30,15 @@ They both also accept a range as environment variable:
```bash
# to migrate any non migrated project from ID 20 to 50.
-export ID_FROM=20
+export ID_FROM=20
export ID_TO=50
```
You can monitor the progress in the **Admin Area > Monitoring > Background Jobs** page.
-There is a specific Queue you can watch to see how long it will take to finish:
+There is a specific Queue you can watch to see how long it will take to finish:
`hashed_storage:hashed_storage_project_migrate`
-After it reaches zero, you can confirm every project has been migrated by running the commands bellow.
+After it reaches zero, you can confirm every project has been migrated by running the commands bellow.
If you find it necessary, you can run this migration script again to schedule missing projects.
Any error or warning will be logged in Sidekiq's log file.
@@ -55,7 +55,7 @@ If you need to rollback the storage migration for any reason, you can follow the
NOTE: **Note:** Hashed Storage will be required in future version of GitLab.
-To prevent new projects from being created in the Hashed storage,
+To prevent new projects from being created in the Hashed storage,
you need to undo the [enable hashed storage][storage-migration] changes.
This task will schedule all your existing projects and associated attachments to be rolled back to the
@@ -77,15 +77,14 @@ Both commands accept a range as environment variable:
```bash
# to rollback any migrated project from ID 20 to 50.
-export ID_FROM=20
+export ID_FROM=20
export ID_TO=50
```
You can monitor the progress in the **Admin Area > Monitoring > Background Jobs** page.
On the **Queues** tab, you can watch the `hashed_storage:hashed_storage_project_rollback` queue to see how long the process will take to finish.
-
-After it reaches zero, you can confirm every project has been rolled back by running the commands bellow.
+After it reaches zero, you can confirm every project has been rolled back by running the commands bellow.
If some projects weren't rolled back, you can run this rollback script again to schedule further rollbacks.
Any error or warning will be logged in Sidekiq's log file.
@@ -106,7 +105,7 @@ 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:
@@ -139,7 +138,7 @@ 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:
@@ -171,7 +170,7 @@ 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:
@@ -203,7 +202,7 @@ 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:
diff --git a/doc/administration/raketasks/uploads/sanitize.md b/doc/administration/raketasks/uploads/sanitize.md
index 54a423b9571..ae5ccfb9e37 100644
--- a/doc/administration/raketasks/uploads/sanitize.md
+++ b/doc/administration/raketasks/uploads/sanitize.md
@@ -4,16 +4,16 @@
You need `exiftool` installed on your system. If you installed GitLab:
-- Using the Omnibus package, you're all set.
-- From source, make sure `exiftool` is installed:
+- Using the Omnibus package, you're all set.
+- From source, make sure `exiftool` is installed:
- ```sh
- # Debian/Ubuntu
- sudo apt-get install libimage-exiftool-perl
+ ```sh
+ # Debian/Ubuntu
+ sudo apt-get install libimage-exiftool-perl
- # RHEL/CentOS
- sudo yum install perl-Image-ExifTool
- ```
+ # RHEL/CentOS
+ sudo yum install perl-Image-ExifTool
+ ```
## Remove EXIF data from existing uploads
diff --git a/doc/administration/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 3de860f9240..ad3a9b19c3c 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -55,8 +55,6 @@ storage2:
> don't specify the full repository path but the parent path), but not for source
> installations.
----
-
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`
@@ -90,8 +88,6 @@ are upgrading from a version prior to 8.10, make sure to add the configuration
as described in the step above. After you make the changes and confirm they are
working, you can remove the `repos_path` line.
----
-
**For Omnibus installations**
1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index f4cb89c84a4..1669f8b128c 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -80,25 +80,20 @@ by another folder with the next 2 characters. They are both stored in a special
### Hashed object pools
-CAUTION: **Beta:**
-Hashed objects pools are considered beta, and are not ready for production use.
-Follow [gitaly#1548](https://gitlab.com/gitlab-org/gitaly/issues/1548) for
-updates.
+> [Introduced](https://gitlab.com/gitlab-org/gitaly/issues/1606) in GitLab 12.1.
-For deduplication of public forks and their parent repository, objects are pooled
-in an object pool. These object pools are a third repository where shared objects
-are stored.
+Forks of public projects are deduplicated by creating a third repository, the object pool, containing the objects from the source project. Using `objects/info/alternates`, the source project and forks use the object pool for shared objects. Objects are moved from the source project to the object pool when housekeeping is run on the source project.
```ruby
# object pool paths
"@pools/#{hash[0..1]}/#{hash[2..3]}/#{hash}.git"
```
-The object pool feature is behind the `object_pools` feature flag, and can be
-enabled for individual projects by executing
-`Feature.enable(:object_pools, Project.find(<id>))`. Note that the project has to
-be on hashed storage, should not be a fork itself, and hashed storage should be
-enabled for all new projects.
+Object pools can be disabled using the `object_pools` feature flag, and can be
+disabled for individual projects by executing
+`Feature.disable(:object_pools, Project.find(<id>))`. Disabling object pools
+will not change existing deduplicated forks, but will prevent new forks from
+being deduplicated.
DANGER: **Danger:**
Do not run `git prune` or `git gc` in pool repositories! This can
@@ -110,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
@@ -138,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 8f7280d5128..098d946a9fa 100644
--- a/doc/administration/troubleshooting/debug.md
+++ b/doc/administration/troubleshooting/debug.md
@@ -10,43 +10,43 @@ an SMTP server, but you're not seeing mail delivered. Here's how to check the se
1. Run a Rails console:
- ```sh
- sudo gitlab-rails console production
- ```
+ ```sh
+ sudo gitlab-rails console production
+ ```
- or for source installs:
+ or for source installs:
- ```sh
- bundle exec rails console production
- ```
+ ```sh
+ bundle exec rails console production
+ ```
1. Look at the ActionMailer `delivery_method` to make sure it matches what you
intended. If you configured SMTP, it should say `:smtp`. If you're using
Sendmail, it should say `:sendmail`:
- ```ruby
- irb(main):001:0> ActionMailer::Base.delivery_method
- => :smtp
- ```
+ ```ruby
+ irb(main):001:0> ActionMailer::Base.delivery_method
+ => :smtp
+ ```
1. If you're using SMTP, check the mail settings:
- ```ruby
- irb(main):002:0> ActionMailer::Base.smtp_settings
- => {:address=>"localhost", :port=>25, :domain=>"localhost.localdomain", :user_name=>nil, :password=>nil, :authentication=>nil, :enable_starttls_auto=>true}```
- ```
+ ```ruby
+ irb(main):002:0> ActionMailer::Base.smtp_settings
+ => {:address=>"localhost", :port=>25, :domain=>"localhost.localdomain", :user_name=>nil, :password=>nil, :authentication=>nil, :enable_starttls_auto=>true}```
+ ```
- In the example above, the SMTP server is configured for the local machine. If this is intended, you may need to check your local mail
- logs (e.g. `/var/log/mail.log`) for more details.
+ In the example above, the SMTP server is configured for the local machine. If this is intended, you may need to check your local mail
+ logs (e.g. `/var/log/mail.log`) for more details.
-1. Send a test message via the console.
+1. Send a test message via the console.
- ```ruby
- irb(main):003:0> Notify.test_email('youremail@email.com', 'Hello World', 'This is a test message').deliver_now
- ```
+ ```ruby
+ irb(main):003:0> Notify.test_email('youremail@email.com', 'Hello World', 'This is a test message').deliver_now
+ ```
- If you do not receive an e-mail and/or see an error message, then check
- your mail server settings.
+ If you do not receive an e-mail and/or see an error message, then check
+ your mail server settings.
## Advanced Issues
@@ -103,37 +103,37 @@ downtime. Otherwise skip to the next section.
1. Run `sudo gdb -p <PID>` to attach to the unicorn process.
1. In the gdb window, type:
- ```
- call (void) rb_backtrace()
- ```
+ ```
+ call (void) rb_backtrace()
+ ```
1. This forces the process to generate a Ruby backtrace. Check
`/var/log/gitlab/unicorn/unicorn_stderr.log` for the backtace. For example, you may see:
- ```ruby
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name'
- ```
+ ```ruby
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name'
+ ```
1. To see the current threads, run:
- ```
- thread apply all bt
- ```
+ ```
+ thread apply all bt
+ ```
1. Once you're done debugging with `gdb`, be sure to detach from the process and exit:
- ```
- detach
- exit
- ```
+ ```
+ detach
+ exit
+ ```
Note that if the unicorn process terminates before you are able to run these
commands, gdb will report an error. To buy more time, you can always raise the
@@ -162,21 +162,21 @@ separate Rails process to debug the issue:
1. Create a Personal Access Token for your user (Profile Settings -> Access Tokens).
1. Bring up the GitLab Rails console. For omnibus users, run:
- ```
- sudo gitlab-rails console
- ```
+ ```
+ sudo gitlab-rails console
+ ```
1. At the Rails console, run:
- ```ruby
- [1] pry(main)> app.get '<URL FROM STEP 2>/?private_token=<TOKEN FROM STEP 3>'
- ```
+ ```ruby
+ [1] pry(main)> app.get '<URL FROM STEP 2>/?private_token=<TOKEN FROM STEP 3>'
+ ```
- For example:
+ For example:
- ```ruby
- [1] pry(main)> app.get 'https://gitlab.com/gitlab-org/gitlab-ce/issues/1?private_token=123456'
- ```
+ ```ruby
+ [1] pry(main)> app.get 'https://gitlab.com/gitlab-org/gitlab-ce/issues/1?private_token=123456'
+ ```
1. In a new window, run `top`. It should show this ruby process using 100% CPU. Write down the PID.
1. Follow step 2 from the previous section on using gdb.
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index c6529812ec3..99f1c386183 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -1,21 +1,18 @@
# Uploads administration
->**Notes:**
Uploads represent all user data that may be sent to GitLab as a single file. As an example, avatars and notes' attachments are uploads. Uploads are integral to GitLab functionality, and therefore cannot be disabled.
## Using local storage
->**Notes:**
+NOTE: **Note:**
This is the default configuration
To change the location where the uploads are stored locally, follow the steps
below.
----
-
**In Omnibus installations:**
->**Notes:**
+NOTE: **Note:**
For historical reasons, uploads are stored into a base directory, which by default is `uploads/-/system`. It is strongly discouraged to change this configuration option on an existing GitLab installation.
_The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._
@@ -30,8 +27,6 @@ _The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**In installations from source:**
_The uploads are stored by default in
@@ -42,8 +37,8 @@ _The uploads are stored by default in
```yaml
uploads:
- storage_path: /mnt/storage
- base_dir: uploads
+ storage_path: /mnt/storage
+ base_dir: uploads
```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -83,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) |
@@ -108,8 +104,8 @@ _The uploads are stored by default in
}
```
- >**Note:**
- >If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
+ NOTE: **Note:**
+ If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
```ruby
gitlab_rails['uploads_object_store_connection'] = {
@@ -122,8 +118,6 @@ _The uploads are stored by default in
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
----
-
**In installations from source:**
_The uploads are stored by default in
@@ -147,6 +141,91 @@ _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:
+
+| Setting | Description | Default |
+|---------|-------------|---------|
+| `provider` | Always `OpenStack` for compatible hosts | OpenStack |
+| `openstack_username` | OpenStack username | |
+| `openstack_api_key` | OpenStack api key | |
+| `openstack_temp_url_key` | OpenStack key for generating temporary urls | |
+| `openstack_auth_url` | OpenStack authentication endpont | |
+| `openstack_region` | OpenStack region | |
+| `openstack_tenant` | OpenStack tenant ID |
+
+**In Omnibus installations:**
+
+_The uploads are stored by default in
+`/var/opt/gitlab/gitlab-rails/public/uploads/-/system`._
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
+ the values you want:
+
+ ```ruby
+ gitlab_rails['uploads_object_store_remote_directory'] = "OPENSTACK_OBJECT_CONTAINER_NAME"
+ gitlab_rails['uploads_object_store_connection'] = {
+ 'provider' => 'OpenStack',
+ 'openstack_username' => 'OPENSTACK_USERNAME',
+ 'openstack_api_key' => 'OPENSTACK_PASSWORD',
+ 'openstack_temp_url_key' => 'OPENSTACK_TEMP_URL_KEY',
+ 'openstack_auth_url' => 'https://auth.cloud.ovh.net/v2.0/',
+ 'openstack_region' => 'DE1',
+ 'openstack_tenant' => 'TENANT_ID',
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
+
+---
+
+**In installations from source:**
+
+_The uploads are stored by default in
+`/home/git/gitlab/public/uploads/-/system`._
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
+ lines:
+
+ ```yaml
+ uploads:
+ object_store:
+ enabled: true
+ direct_upload: false
+ background_upload: true
+ proxy_download: false
+ remote_directory: OPENSTACK_OBJECT_CONTAINER_NAME
+ connection:
+ provider: OpenStack
+ openstack_username: OPENSTACK_USERNAME
+ openstack_api_key: OPENSTACK_PASSWORD
+ openstack_temp_url_key: OPENSTACK_TEMP_URL_KEY
+ openstack_auth_url: 'https://auth.cloud.ovh.net/v2.0/'
+ openstack_region: DE1
+ openstack_tenant: 'TENANT_ID'
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
+
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
[restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab"
[eep]: https://about.gitlab.com/gitlab-ee/ "GitLab Premium"
diff --git a/doc/api/README.md b/doc/api/README.md
index 3ded230370c..f9cc1a09870 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -29,6 +29,7 @@ The following API resources are available in the project context:
| [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) |
@@ -154,9 +155,10 @@ the `/Users` endpoint. The base URL is: `/api/scim/v2/groups/:group_path/Users/`
## Road to GraphQL
-Going forward, we will start on moving to
-[GraphQL](graphql/index.md) and deprecate the use of
-controller-specific endpoints. GraphQL has a number of benefits:
+[GraphQL](graphql/index.md) is available in GitLab, which will
+allow deprecation of controller-specific endpoints.
+
+GraphQL has a number of benefits:
1. We avoid having to maintain two different APIs.
1. Callers of the API can request only what they need.
@@ -270,6 +272,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
@@ -320,8 +328,6 @@ By default, impersonation is enabled. To disable impersonation:
To re-enable impersonation, remove this configuration and reconfigure GitLab.
----
-
**For installations from source**
1. Edit `config/gitlab.yml`:
@@ -380,8 +386,6 @@ returned with status code `404`:
}
```
----
-
Example of a valid API call and a request using cURL with sudo request,
providing a username:
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 25015fad9e3..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"
@@ -716,6 +718,7 @@ Example response if commit is signed:
```
Example response if commit is unsigned:
+
```json
{
"message": "404 GPG Signature Not Found"
diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md
index 64ea15bca93..174b93a4f7a 100644
--- a/doc/api/container_registry.md
+++ b/doc/api/container_registry.md
@@ -193,13 +193,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
new file mode 100644
index 00000000000..ed5ebdade19
--- /dev/null
+++ b/doc/api/dependencies.md
@@ -0,0 +1,50 @@
+# Dependencies API **(ULTIMATE)**
+
+CAUTION: **Caution:**
+This API is in an alpha stage and considered unstable.
+The response payload may be subject to change or breakage
+across GitLab releases.
+
+Every call to this endpoint requires authentication. To perform this call, user should be authorized to read
+[Project Security Dashboard](../user/application_security/security_dashboard/index.md#project-security-dashboard).
+
+## 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.
+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
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `package_manager` | string array | no | Returns dependencies belonging to specified package manager. Valid values: `bundler`, `composer`, `maven`, `npm`, `pip` or `yarn`. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/dependencies
+```
+
+Example response:
+
+```json
+[
+ {
+ "name": "rails",
+ "version": "5.0.1",
+ "package_manager": "bundler",
+ "dependency_file_path": "Gemfile.lock"
+ },
+ {
+ "name": "hanami",
+ "version": "1.3.1",
+ "package_manager": "bundler",
+ "dependency_file_path": "Gemfile.lock"
+ }
+]
+```
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index 208b8dca2e2..b4a2d0b15f6 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -12,16 +12,15 @@ This includes system notes, which are notes about changes to the object (for exa
## Discussions pagination
-By default, `GET` requests return 20 results at a time because the API results
-are paginated.
+By default, `GET` requests return 20 results at a time because the API results are paginated.
Read more on [pagination](README.md#pagination).
## Issues
-### List project issue discussions
+### List project issue discussion items
-Gets a list of all discussions for a single issue.
+Gets a list of all discussion items for a single issue.
```
GET /projects/:id/issues/:issue_iid/discussions
@@ -115,9 +114,9 @@ GET /projects/:id/issues/:issue_iid/discussions
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions
```
-### Get single issue discussion
+### Get single issue discussion item
-Returns a single discussion for a specific project issue
+Returns a single discussion item for a specific project issue
```
GET /projects/:id/issues/:issue_iid/discussions/:discussion_id
@@ -129,16 +128,15 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `issue_iid` | integer | yes | The IID of an issue |
-| `discussion_id` | integer | yes | The ID of a discussion |
+| `discussion_id` | integer | yes | The ID of a discussion item |
```bash
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
-### Create new issue discussion
+### Create new issue thread
-Creates a new discussion to a single project issue. This is similar to creating
-a note but other comments (replies) can be added to it later.
+Creates a new thread to a single project issue. This is similar to creating a note but other comments (replies) can be added to it later.
```
POST /projects/:id/issues/:issue_iid/discussions
@@ -150,17 +148,19 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `issue_iid` | integer | yes | The IID of an issue |
-| `body` | string | yes | The content of a discussion |
+| `body` | string | yes | The content of the thread |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions?body=comment
```
-### Add note to existing issue discussion
+### Add note to existing issue thread
+
+Adds a new note to the thread. This can also [create a thread from a single comment](../user/discussions/#start-a-thread-by-replying-to-a-standard-comment).
-Adds a new note to the discussion. This can also
-[create a discussion from a single comment](../user/discussions/#start-a-discussion-by-replying-to-a-standard-comment).
+**WARNING**
+Notes can be added to other items than comments (system notes, etc.) making them threads.
```
POST /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes
@@ -172,18 +172,18 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `issue_iid` | integer | yes | The IID of an issue |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | yes | The content of a discussion |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | yes | The content of the note/reply |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
-### Modify existing issue discussion note
+### Modify existing issue thread note
-Modify existing discussion note of an issue.
+Modify existing thread note of an issue.
```
PUT /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes/:note_id
@@ -195,17 +195,17 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `issue_iid` | integer | yes | The IID of an issue |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | yes | The content of a discussion |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | yes | The content of the note/reply |
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/issues/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
```
-### Delete an issue discussion note
+### Delete an issue thread note
-Deletes an existing discussion note of an issue.
+Deletes an existing thread note of an issue.
```
DELETE /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes/:note_id
@@ -226,9 +226,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitl
## Snippets
-### List project snippet discussions
+### List project snippet discussion items
-Gets a list of all discussions for a single snippet.
+Gets a list of all discussion items for a single snippet.
```
GET /projects/:id/snippets/:snippet_id/discussions
@@ -322,9 +322,9 @@ GET /projects/:id/snippets/:snippet_id/discussions
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions
```
-### Get single snippet discussion
+### Get single snippet discussion item
-Returns a single discussion for a specific project snippet
+Returns a single discussion item for a specific project snippet
```
GET /projects/:id/snippets/:snippet_id/discussions/:discussion_id
@@ -336,15 +336,15 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `snippet_id` | integer | yes | The ID of an snippet |
-| `discussion_id` | integer | yes | The ID of a discussion |
+| `discussion_id` | integer | yes | The ID of a discussion item |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
-### Create new snippet discussion
+### Create new snippet thread
-Creates a new discussion to a single project snippet. This is similar to creating
+Creates a new thread to a single project snippet. This is similar to creating
a note but other comments (replies) can be added to it later.
```
@@ -364,9 +364,9 @@ Parameters:
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions?body=comment
```
-### Add note to existing snippet discussion
+### Add note to existing snippet thread
-Adds a new note to the discussion.
+Adds a new note to the thread.
```
POST /projects/:id/snippets/:snippet_id/discussions/:discussion_id/notes
@@ -378,18 +378,18 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `snippet_id` | integer | yes | The ID of an snippet |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | yes | The content of a discussion |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | yes | The content of the note/reply |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
-### Modify existing snippet discussion note
+### Modify existing snippet thread note
-Modify existing discussion note of an snippet.
+Modify existing thread note of a snippet.
```
PUT /projects/:id/snippets/:snippet_id/discussions/:discussion_id/notes/:note_id
@@ -401,17 +401,17 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `snippet_id` | integer | yes | The ID of an snippet |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | yes | The content of a discussion |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | yes | The content of the note/reply |
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
```
-### Delete an snippet discussion note
+### Delete a snippet thread note
-Deletes an existing discussion note of an snippet.
+Deletes an existing thread note of a snippet.
```
DELETE /projects/:id/snippets/:snippet_id/discussions/:discussion_id/notes/:note_id
@@ -432,9 +432,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitl
## Epics **(ULTIMATE)**
-### List group epic discussions
+### List group epic discussion items
-Gets a list of all discussions for a single epic.
+Gets a list of all discussion items for a single epic.
```
GET /groups/:id/epics/:epic_id/discussions
@@ -529,9 +529,9 @@ GET /groups/:id/epics/:epic_id/discussions
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions
```
-### Get single epic discussion
+### Get single epic discussion item
-Returns a single discussion for a specific group epic
+Returns a single discussion item for a specific group epic
```
GET /groups/:id/epics/:epic_id/discussions/:discussion_id
@@ -543,16 +543,16 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `epic_id` | integer | yes | The ID of an epic |
-| `discussion_id` | integer | yes | The ID of a discussion |
+| `discussion_id` | integer | yes | The ID of a discussion item |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
-### Create new epic discussion
+### Create new epic thread
-Creates a new discussion to a single group epic. This is similar to creating
-a note but but another comments (replies) can be added to it later.
+Creates a new thread to a single group epic. This is similar to creating
+a note but but other comments (replies) can be added to it later.
```
POST /groups/:id/epics/:epic_id/discussions
@@ -564,17 +564,17 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `epic_id` | integer | yes | The ID of an epic |
-| `body` | string | yes | The content of a discussion |
+| `body` | string | yes | The content of the thread |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions?body=comment
```
-### Add note to existing epic discussion
+### Add note to existing epic thread
-Adds a new note to the discussion. This can also
-[create a discussion from a single comment](../user/discussions/#start-a-discussion-by-replying-to-a-standard-comment).
+Adds a new note to the thread. This can also
+[create a thread from a single comment](../user/discussions/#start-a-thread-by-replying-to-a-standard-comment).
```
POST /groups/:id/epics/:epic_id/discussions/:discussion_id/notes
@@ -585,19 +585,19 @@ Parameters:
| Attribute | Type | Required | Description |
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
-| `epic_id` | integer | yes | The ID of an epic |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | yes | The content of a discussion |
+| `epic_id` | integer | yes | The ID of an epic |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | yes | The content of the note/reply |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
-### Modify existing epic discussion note
+### Modify existing epic thread note
-Modify existing discussion note of an epic.
+Modify existing thread note of an epic.
```
PUT /groups/:id/epics/:epic_id/discussions/:discussion_id/notes/:note_id
@@ -609,17 +609,17 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `epic_id` | integer | yes | The ID of an epic |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | yes | The content of a discussion |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | yes | The content of note/reply |
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment
```
-### Delete an epic discussion note
+### Delete an epic thread note
-Deletes an existing discussion note of an epic.
+Deletes an existing thread note of an epic.
```
DELETE /groups/:id/epics/:epic_id/discussions/:discussion_id/notes/:note_id
@@ -631,8 +631,8 @@ Parameters:
| --------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
| `epic_id` | integer | yes | The ID of an epic |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/epics/11/discussions/636
@@ -640,9 +640,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitl
## Merge requests
-### List project merge request discussions
+### List project merge request discussion items
-Gets a list of all discussions for a single merge request.
+Gets a list of all discussion items for a single merge request.
```
GET /projects/:id/merge_requests/:merge_request_iid/discussions
@@ -789,9 +789,9 @@ Diff comments contain also position:
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions
```
-### Get single merge request discussion
+### Get single merge request discussion item
-Returns a single discussion for a specific project merge request
+Returns a single discussion item for a specific project merge request
```
GET /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id
@@ -803,15 +803,15 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `merge_request_iid` | integer | yes | The IID of a merge request |
-| `discussion_id` | integer | yes | The ID of a discussion |
+| `discussion_id` | integer | yes | The ID of a discussion item |
```bash
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
-### Create new merge request discussion
+### Create new merge request thread
-Creates a new discussion to a single project merge request. This is similar to creating
+Creates a new thread to a single project merge request. This is similar to creating
a note but other comments (replies) can be added to it later.
```
@@ -824,7 +824,7 @@ Parameters:
| ------------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `merge_request_iid` | integer | yes | The IID of a merge request |
-| `body` | string | yes | The content of a discussion |
+| `body` | string | yes | The content of the thread |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
| `position` | hash | no | Position when creating a diff note |
| `position[base_sha]` | string | yes | Base commit SHA in the source branch |
@@ -844,9 +844,9 @@ Parameters:
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions?body=comment
```
-### Resolve a merge request discussion
+### Resolve a merge request thread
-Resolve/unresolve whole discussion of a merge request.
+Resolve/unresolve whole thread of a merge request.
```
PUT /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id
@@ -858,17 +858,17 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `merge_request_iid` | integer | yes | The IID of a merge request |
-| `discussion_id` | integer | yes | The ID of a discussion |
+| `discussion_id` | integer | yes | The ID of a thread |
| `resolved` | boolean | yes | Resolve/unresolve the discussion |
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7?resolved=true
```
-### Add note to existing merge request discussion
+### Add note to existing merge request thread
-Adds a new note to the discussion. This can also
-[create a discussion from a single comment](../user/discussions/#start-a-discussion-by-replying-to-a-standard-comment).
+Adds a new note to the thread. This can also
+[create a thread from a single comment](../user/discussions/#start-a-thread-by-replying-to-a-standard-comment).
```
POST /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes
@@ -880,18 +880,18 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `merge_request_iid` | integer | yes | The IID of a merge request |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | yes | The content of a discussion |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | yes | The content of the note/reply |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
-### Modify an existing merge request discussion note
+### Modify an existing merge request thread note
-Modify or resolve an existing discussion note of a merge request.
+Modify or resolve an existing thread note of a merge request.
```
PUT /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes/:note_id
@@ -903,9 +903,9 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `merge_request_iid` | integer | yes | The IID of a merge request |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | no | The content of a discussion (exactly one of `body` or `resolved` must be set |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | no | The content of the note/reply (exactly one of `body` or `resolved` must be set |
| `resolved` | boolean | no | Resolve/unresolve the note (exactly one of `body` or `resolved` must be set |
```bash
@@ -918,9 +918,9 @@ Resolving a note:
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved=true
```
-### Delete a merge request discussion note
+### Delete a merge request thread note
-Deletes an existing discussion note of a merge request.
+Deletes an existing thread note of a merge request.
```
DELETE /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes/:note_id
@@ -932,8 +932,8 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `merge_request_iid` | integer | yes | The IID of a merge request |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/636
@@ -941,9 +941,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitl
## Commits
-### List project commit discussions
+### List project commit discussion items
-Gets a list of all discussions for a single commit.
+Gets a list of all discussion items for a single commit.
```
GET /projects/:id/commits/:commit_id/discussions
@@ -1082,9 +1082,9 @@ Diff comments contain also position:
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions
```
-### Get single commit discussion
+### Get single commit discussion item
-Returns a single discussion for a specific project commit
+Returns a single discussion item for a specific project commit
```
GET /projects/:id/commits/:commit_id/discussions/:discussion_id
@@ -1096,15 +1096,15 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `commit_id` | integer | yes | The ID of a commit |
-| `discussion_id` | integer | yes | The ID of a discussion |
+| `discussion_id` | integer | yes | The ID of a discussion item |
```bash
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7
```
-### Create new commit discussion
+### Create new commit thread
-Creates a new discussion to a single project commit. This is similar to creating
+Creates a new thread to a single project commit. This is similar to creating
a note but other comments (replies) can be added to it later.
```
@@ -1117,7 +1117,7 @@ Parameters:
| ------------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `commit_id` | integer | yes | The ID of a commit |
-| `body` | string | yes | The content of a discussion |
+| `body` | string | yes | The content of the thread |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
| `position` | hash | no | Position when creating a diff note |
| `position[base_sha]` | string | yes | Base commit SHA in the source branch |
@@ -1137,9 +1137,9 @@ Parameters:
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions?body=comment
```
-### Add note to existing commit discussion
+### Add note to existing commit thread
-Adds a new note to the discussion.
+Adds a new note to the thread.
```
POST /projects/:id/commits/:commit_id/discussions/:discussion_id/notes
@@ -1151,18 +1151,18 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `commit_id` | integer | yes | The ID of a commit |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
-| `body` | string | yes | The content of a discussion |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
+| `body` | string | yes | The content of the note/reply |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z (requires admin or project/group owner rights) |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment
```
-### Modify an existing commit discussion note
+### Modify an existing commit thread note
-Modify or resolve an existing discussion note of a commit.
+Modify or resolve an existing thread note of a commit.
```
PUT /projects/:id/commits/:commit_id/discussions/:discussion_id/notes/:note_id
@@ -1174,8 +1174,8 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `commit_id` | integer | yes | The ID of a commit |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
| `body` | string | no | The content of a note |
```bash
@@ -1188,9 +1188,9 @@ Resolving a note:
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved=true
```
-### Delete a commit discussion note
+### Delete a commit thread note
-Deletes an existing discussion note of a commit.
+Deletes an existing thread note of a commit.
```
DELETE /projects/:id/commits/:commit_id/discussions/:discussion_id/notes/:note_id
@@ -1202,8 +1202,8 @@ Parameters:
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `commit_id` | integer | yes | The ID of a commit |
-| `discussion_id` | integer | yes | The ID of a discussion |
-| `note_id` | integer | yes | The ID of a discussion note |
+| `discussion_id` | integer | yes | The ID of a thread |
+| `note_id` | integer | yes | The ID of a thread note |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/636
diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md
index 3bf96e279b7..bdc7c1959d2 100644
--- a/doc/api/graphql/index.md
+++ b/doc/api/graphql/index.md
@@ -1,6 +1,8 @@
-# GraphQL API (Alpha)
+# GraphQL API
-> [Introduced][ce-19008] in GitLab 11.0.
+> - [Introduced][ce-19008] in GitLab 11.0 (enabled by feature flag `graphql`).
+> - [Always enabled](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30444)
+ in GitLab 12.1.
[GraphQL](https://graphql.org/) is a query language for APIs that
allows clients to request exactly the data they need, making it
@@ -23,24 +25,12 @@ programmatically with GitLab. To achieve this, it needs full coverage - anything
possible in the REST API should also be possible in the GraphQL API.
To help us meet this vision, the frontend should use GraphQL in preference to
-the REST API for new features, although the alpha status of GraphQL may prevent
-this from being a possibility at times.
+the REST API for new features.
There are no plans to deprecate the REST API. To reduce the technical burden of
supporting two APIs in parallel, they should share implementations as much as
possible.
-## Enabling the GraphQL feature
-
-The GraphQL API itself is currently in Alpha, and therefore hidden behind a
-feature flag. You can enable the feature using the [features api][features-api] on a self-hosted instance.
-
-For example:
-
-```shell
-curl --data "value=100" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features/graphql
-```
-
## Available queries
A first iteration of a GraphQL API includes the following queries
@@ -57,6 +47,12 @@ info about multiplexed queries is also available for
[graphql-ruby](https://graphql-ruby.org/queries/multiplex.html) the
library GitLab uses on the backend.
+## Reference
+
+GitLab's GraphQL reference [is available](reference/index.md).
+
+It is automatically generated from GitLab's GraphQL schema and embedded in a Markdown file.
+
## GraphiQL
The API can be explored by using the GraphiQL IDE, it is available on your
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
new file mode 100644
index 00000000000..2d3bec4ff67
--- /dev/null
+++ b/doc/api/graphql/reference/index.md
@@ -0,0 +1,507 @@
+<!---
+ This documentation is auto generated by a script.
+
+ Please do not edit this file directly, check compile_docs task on lib/tasks/gitlab/graphql.rake.
+--->
+
+# GraphQL API Resources
+
+This documentation is self-generated based on GitLab current GraphQL schema.
+
+The API can be explored interactively using the [GraphiQL IDE](../index.md#graphiql).
+
+## Objects
+
+### AddAwardEmojiPayload
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Reasons why the mutation failed. |
+| `awardEmoji` | AwardEmoji | The award emoji after mutation |
+
+### AwardEmoji
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `name` | String! | The emoji name |
+| `description` | String! | The emoji description |
+| `unicode` | String! | The emoji in unicode |
+| `emoji` | String! | The emoji as an icon |
+| `unicodeVersion` | String! | The unicode version for this emoji |
+| `user` | User! | The user who awarded the emoji |
+
+### Blob
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `id` | ID! | |
+| `name` | String! | |
+| `type` | EntryType! | |
+| `path` | String! | |
+| `flatPath` | String! | |
+| `webUrl` | String | |
+| `lfsOid` | String | |
+
+### Commit
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `id` | ID! | |
+| `sha` | String! | |
+| `title` | String | |
+| `description` | String | |
+| `message` | String | |
+| `authoredDate` | Time | |
+| `webUrl` | String! | |
+| `author` | User | |
+| `latestPipeline` | Pipeline | Latest pipeline for this commit |
+
+### DetailedStatus
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `group` | String! | |
+| `icon` | String! | |
+| `favicon` | String! | |
+| `detailsPath` | String! | |
+| `hasDetails` | Boolean! | |
+| `label` | String! | |
+| `text` | String! | |
+| `tooltip` | String! | |
+
+### DiffPosition
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `headSha` | String! | The sha of the head at the time the comment was made |
+| `baseSha` | String | The merge base of the branch the comment was made on |
+| `startSha` | String! | The sha of the branch being compared against |
+| `filePath` | String! | The path of the file that was changed |
+| `oldPath` | String | The path of the file on the start sha. |
+| `newPath` | String | The path of the file on the head sha. |
+| `positionType` | DiffPositionType! | |
+| `oldLine` | Int | The line on start sha that was changed |
+| `newLine` | Int | The line on head sha that was changed |
+| `x` | Int | The X postion on which the comment was made |
+| `y` | Int | The Y position on which the comment was made |
+| `width` | Int | The total width of the image |
+| `height` | Int | The total height of the image |
+
+### Discussion
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `id` | ID! | |
+| `createdAt` | Time! | |
+
+### Group
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `id` | ID! | |
+| `name` | String! | |
+| `path` | String! | |
+| `fullName` | String! | |
+| `fullPath` | ID! | |
+| `description` | String | |
+| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
+| `visibility` | String | |
+| `lfsEnabled` | Boolean | |
+| `requestAccessEnabled` | Boolean | |
+| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
+| `webUrl` | String! | |
+| `avatarUrl` | String | |
+| `parent` | Group | |
+
+### GroupPermissions
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `readGroup` | Boolean! | Whether or not a user can perform `read_group` on this resource |
+
+### Issue
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `userPermissions` | IssuePermissions! | Permissions for the current user on the resource |
+| `iid` | ID! | |
+| `title` | String! | |
+| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
+| `description` | String | |
+| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
+| `state` | IssueState! | |
+| `reference` | String! | |
+| `author` | User! | |
+| `milestone` | Milestone | |
+| `dueDate` | Time | |
+| `confidential` | Boolean! | |
+| `discussionLocked` | Boolean! | |
+| `upvotes` | Int! | |
+| `downvotes` | Int! | |
+| `userNotesCount` | Int! | |
+| `webPath` | String! | |
+| `webUrl` | String! | |
+| `relativePosition` | Int | |
+| `closedAt` | Time | |
+| `createdAt` | Time! | |
+| `updatedAt` | Time! | |
+| `taskCompletionStatus` | TaskCompletionStatus! | |
+
+### IssuePermissions
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `readIssue` | Boolean! | Whether or not a user can perform `read_issue` on this resource |
+| `adminIssue` | Boolean! | Whether or not a user can perform `admin_issue` on this resource |
+| `updateIssue` | Boolean! | Whether or not a user can perform `update_issue` on this resource |
+| `createNote` | Boolean! | Whether or not a user can perform `create_note` on this resource |
+| `reopenIssue` | Boolean! | Whether or not a user can perform `reopen_issue` on this resource |
+
+### Label
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `description` | String | |
+| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
+| `title` | String! | |
+| `color` | String! | |
+| `textColor` | String! | |
+
+### MergeRequest
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `userPermissions` | MergeRequestPermissions! | Permissions for the current user on the resource |
+| `id` | ID! | |
+| `iid` | String! | |
+| `title` | String! | |
+| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
+| `description` | String | |
+| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
+| `state` | MergeRequestState! | |
+| `createdAt` | Time! | |
+| `updatedAt` | Time! | |
+| `sourceProject` | Project | |
+| `targetProject` | Project! | |
+| `project` | Project! | |
+| `projectId` | Int! | |
+| `sourceProjectId` | Int | |
+| `targetProjectId` | Int! | |
+| `sourceBranch` | String! | |
+| `targetBranch` | String! | |
+| `workInProgress` | Boolean! | |
+| `mergeWhenPipelineSucceeds` | Boolean | |
+| `diffHeadSha` | String | |
+| `mergeCommitSha` | String | |
+| `userNotesCount` | Int | |
+| `shouldRemoveSourceBranch` | Boolean | |
+| `forceRemoveSourceBranch` | Boolean | |
+| `mergeStatus` | String | |
+| `inProgressMergeCommitSha` | String | |
+| `mergeError` | String | |
+| `allowCollaboration` | Boolean | |
+| `shouldBeRebased` | Boolean! | |
+| `rebaseCommitSha` | String | |
+| `rebaseInProgress` | Boolean! | |
+| `mergeCommitMessage` | String | |
+| `defaultMergeCommitMessage` | String | |
+| `mergeOngoing` | Boolean! | |
+| `sourceBranchExists` | Boolean! | |
+| `mergeableDiscussionsState` | Boolean | |
+| `webUrl` | String | |
+| `upvotes` | Int! | |
+| `downvotes` | Int! | |
+| `subscribed` | Boolean! | |
+| `headPipeline` | Pipeline | |
+| `taskCompletionStatus` | TaskCompletionStatus! | |
+
+### MergeRequestPermissions
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `readMergeRequest` | Boolean! | Whether or not a user can perform `read_merge_request` on this resource |
+| `adminMergeRequest` | Boolean! | Whether or not a user can perform `admin_merge_request` on this resource |
+| `updateMergeRequest` | Boolean! | Whether or not a user can perform `update_merge_request` on this resource |
+| `createNote` | Boolean! | Whether or not a user can perform `create_note` on this resource |
+| `pushToSourceBranch` | Boolean! | Whether or not a user can perform `push_to_source_branch` on this resource |
+| `removeSourceBranch` | Boolean! | Whether or not a user can perform `remove_source_branch` on this resource |
+| `cherryPickOnCurrentMergeRequest` | Boolean! | Whether or not a user can perform `cherry_pick_on_current_merge_request` on this resource |
+| `revertOnCurrentMergeRequest` | Boolean! | Whether or not a user can perform `revert_on_current_merge_request` on this resource |
+
+### MergeRequestSetWipPayload
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Reasons why the mutation failed. |
+| `mergeRequest` | MergeRequest | The merge request after mutation |
+
+### Metadata
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `version` | String! | |
+| `revision` | String! | |
+
+### Milestone
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `description` | String | |
+| `title` | String! | |
+| `state` | String! | |
+| `dueDate` | Time | |
+| `startDate` | Time | |
+| `createdAt` | Time! | |
+| `updatedAt` | Time! | |
+
+### Namespace
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `id` | ID! | |
+| `name` | String! | |
+| `path` | String! | |
+| `fullName` | String! | |
+| `fullPath` | ID! | |
+| `description` | String | |
+| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
+| `visibility` | String | |
+| `lfsEnabled` | Boolean | |
+| `requestAccessEnabled` | Boolean | |
+
+### Note
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `userPermissions` | NotePermissions! | Permissions for the current user on the resource |
+| `id` | ID! | |
+| `project` | Project | The project this note is associated to |
+| `author` | User! | The user who wrote this note |
+| `resolvedBy` | User | The user that resolved the discussion |
+| `system` | Boolean! | Whether or not this note was created by the system or by a user |
+| `body` | String! | The content note itself |
+| `bodyHtml` | String | The GitLab Flavored Markdown rendering of `note` |
+| `createdAt` | Time! | |
+| `updatedAt` | Time! | |
+| `discussion` | Discussion | The discussion this note is a part of |
+| `resolvable` | Boolean! | |
+| `resolvedAt` | Time | The time the discussion was resolved |
+| `position` | DiffPosition | The position of this note on a diff |
+
+### NotePermissions
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `readNote` | Boolean! | Whether or not a user can perform `read_note` on this resource |
+| `createNote` | Boolean! | Whether or not a user can perform `create_note` on this resource |
+| `adminNote` | Boolean! | Whether or not a user can perform `admin_note` on this resource |
+| `resolveNote` | Boolean! | Whether or not a user can perform `resolve_note` on this resource |
+| `awardEmoji` | Boolean! | Whether or not a user can perform `award_emoji` on this resource |
+
+### PageInfo
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `hasNextPage` | Boolean! | When paginating forwards, are there more items? |
+| `hasPreviousPage` | Boolean! | When paginating backwards, are there more items? |
+| `startCursor` | String | When paginating backwards, the cursor to continue. |
+| `endCursor` | String | When paginating forwards, the cursor to continue. |
+
+### Pipeline
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `userPermissions` | PipelinePermissions! | Permissions for the current user on the resource |
+| `id` | ID! | |
+| `iid` | String! | |
+| `sha` | String! | |
+| `beforeSha` | String | |
+| `status` | PipelineStatusEnum! | |
+| `detailedStatus` | DetailedStatus! | |
+| `duration` | Int | Duration of the pipeline in seconds |
+| `coverage` | Float | Coverage percentage |
+| `createdAt` | Time! | |
+| `updatedAt` | Time! | |
+| `startedAt` | Time | |
+| `finishedAt` | Time | |
+| `committedAt` | Time | |
+
+### PipelinePermissions
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `updatePipeline` | Boolean! | Whether or not a user can perform `update_pipeline` on this resource |
+| `adminPipeline` | Boolean! | Whether or not a user can perform `admin_pipeline` on this resource |
+| `destroyPipeline` | Boolean! | Whether or not a user can perform `destroy_pipeline` on this resource |
+
+### Project
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `userPermissions` | ProjectPermissions! | Permissions for the current user on the resource |
+| `id` | ID! | |
+| `fullPath` | ID! | |
+| `path` | String! | |
+| `nameWithNamespace` | String! | |
+| `name` | String! | |
+| `description` | String | |
+| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
+| `tagList` | String | |
+| `sshUrlToRepo` | String | |
+| `httpUrlToRepo` | String | |
+| `webUrl` | String | |
+| `starCount` | Int! | |
+| `forksCount` | Int! | |
+| `createdAt` | Time | |
+| `lastActivityAt` | Time | |
+| `archived` | Boolean | |
+| `visibility` | String | |
+| `containerRegistryEnabled` | Boolean | |
+| `sharedRunnersEnabled` | Boolean | |
+| `lfsEnabled` | Boolean | |
+| `mergeRequestsFfOnlyEnabled` | Boolean | |
+| `avatarUrl` | String | |
+| `issuesEnabled` | Boolean | |
+| `mergeRequestsEnabled` | Boolean | |
+| `wikiEnabled` | Boolean | |
+| `snippetsEnabled` | Boolean | |
+| `jobsEnabled` | Boolean | |
+| `publicJobs` | Boolean | |
+| `openIssuesCount` | Int | |
+| `importStatus` | String | |
+| `onlyAllowMergeIfPipelineSucceeds` | Boolean | |
+| `requestAccessEnabled` | Boolean | |
+| `onlyAllowMergeIfAllDiscussionsAreResolved` | Boolean | |
+| `printingMergeRequestLinkEnabled` | Boolean | |
+| `namespace` | Namespace | |
+| `group` | Group | |
+| `statistics` | ProjectStatistics | |
+| `repository` | Repository | |
+| `mergeRequest` | MergeRequest | |
+| `issue` | Issue | |
+
+### ProjectPermissions
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `changeNamespace` | Boolean! | Whether or not a user can perform `change_namespace` on this resource |
+| `changeVisibilityLevel` | Boolean! | Whether or not a user can perform `change_visibility_level` on this resource |
+| `renameProject` | Boolean! | Whether or not a user can perform `rename_project` on this resource |
+| `removeProject` | Boolean! | Whether or not a user can perform `remove_project` on this resource |
+| `archiveProject` | Boolean! | Whether or not a user can perform `archive_project` on this resource |
+| `removeForkProject` | Boolean! | Whether or not a user can perform `remove_fork_project` on this resource |
+| `removePages` | Boolean! | Whether or not a user can perform `remove_pages` on this resource |
+| `readProject` | Boolean! | Whether or not a user can perform `read_project` on this resource |
+| `createMergeRequestIn` | Boolean! | Whether or not a user can perform `create_merge_request_in` on this resource |
+| `readWiki` | Boolean! | Whether or not a user can perform `read_wiki` on this resource |
+| `readProjectMember` | Boolean! | Whether or not a user can perform `read_project_member` on this resource |
+| `createIssue` | Boolean! | Whether or not a user can perform `create_issue` on this resource |
+| `uploadFile` | Boolean! | Whether or not a user can perform `upload_file` on this resource |
+| `readCycleAnalytics` | Boolean! | Whether or not a user can perform `read_cycle_analytics` on this resource |
+| `downloadCode` | Boolean! | Whether or not a user can perform `download_code` on this resource |
+| `downloadWikiCode` | Boolean! | Whether or not a user can perform `download_wiki_code` on this resource |
+| `forkProject` | Boolean! | Whether or not a user can perform `fork_project` on this resource |
+| `createProjectSnippet` | Boolean! | Whether or not a user can perform `create_project_snippet` on this resource |
+| `readCommitStatus` | Boolean! | Whether or not a user can perform `read_commit_status` on this resource |
+| `requestAccess` | Boolean! | Whether or not a user can perform `request_access` on this resource |
+| `createPipeline` | Boolean! | Whether or not a user can perform `create_pipeline` on this resource |
+| `createPipelineSchedule` | Boolean! | Whether or not a user can perform `create_pipeline_schedule` on this resource |
+| `createMergeRequestFrom` | Boolean! | Whether or not a user can perform `create_merge_request_from` on this resource |
+| `createWiki` | Boolean! | Whether or not a user can perform `create_wiki` on this resource |
+| `pushCode` | Boolean! | Whether or not a user can perform `push_code` on this resource |
+| `createDeployment` | Boolean! | Whether or not a user can perform `create_deployment` on this resource |
+| `pushToDeleteProtectedBranch` | Boolean! | Whether or not a user can perform `push_to_delete_protected_branch` on this resource |
+| `adminWiki` | Boolean! | Whether or not a user can perform `admin_wiki` on this resource |
+| `adminProject` | Boolean! | Whether or not a user can perform `admin_project` on this resource |
+| `updatePages` | Boolean! | Whether or not a user can perform `update_pages` on this resource |
+| `adminRemoteMirror` | Boolean! | Whether or not a user can perform `admin_remote_mirror` on this resource |
+| `createLabel` | Boolean! | Whether or not a user can perform `create_label` on this resource |
+| `updateWiki` | Boolean! | Whether or not a user can perform `update_wiki` on this resource |
+| `destroyWiki` | Boolean! | Whether or not a user can perform `destroy_wiki` on this resource |
+| `createPages` | Boolean! | Whether or not a user can perform `create_pages` on this resource |
+| `destroyPages` | Boolean! | Whether or not a user can perform `destroy_pages` on this resource |
+| `readPagesContent` | Boolean! | Whether or not a user can perform `read_pages_content` on this resource |
+
+### ProjectStatistics
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `commitCount` | Int! | |
+| `storageSize` | Int! | |
+| `repositorySize` | Int! | |
+| `lfsObjectsSize` | Int! | |
+| `buildArtifactsSize` | Int! | |
+| `packagesSize` | Int! | |
+| `wikiSize` | Int | |
+
+### RemoveAwardEmojiPayload
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Reasons why the mutation failed. |
+| `awardEmoji` | AwardEmoji | The award emoji after mutation |
+
+### Repository
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `rootRef` | String | |
+| `empty` | Boolean! | |
+| `exists` | Boolean! | |
+| `tree` | Tree | |
+
+### Submodule
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `id` | ID! | |
+| `name` | String! | |
+| `type` | EntryType! | |
+| `path` | String! | |
+| `flatPath` | String! | |
+
+### TaskCompletionStatus
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `count` | Int! | |
+| `completedCount` | Int! | |
+
+### ToggleAwardEmojiPayload
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
+| `errors` | String! => Array | Reasons why the mutation failed. |
+| `awardEmoji` | AwardEmoji | The award emoji after mutation |
+| `toggledOn` | Boolean! | True when the emoji was awarded, false when it was removed |
+
+### Tree
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `lastCommit` | Commit | |
+
+### TreeEntry
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `id` | ID! | |
+| `name` | String! | |
+| `type` | EntryType! | |
+| `path` | String! | |
+| `flatPath` | String! | |
+| `webUrl` | String | |
+
+### User
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `name` | String! | |
+| `username` | String! | |
+| `avatarUrl` | String! | |
+| `webUrl` | String! | |
+
diff --git a/doc/api/group_clusters.md b/doc/api/group_clusters.md
new file mode 100644
index 00000000000..31c0e6abead
--- /dev/null
+++ b/doc/api/group_clusters.md
@@ -0,0 +1,280 @@
+# Group clusters API
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30213)
+in GitLab 12.1.
+
+NOTE: **Note:**
+User will need at least maintainer access for the group to use these endpoints.
+
+## List group clusters
+
+Returns a list of group clusters.
+
+```
+GET /groups/:id/clusters
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/groups/26/clusters
+```
+
+Example response:
+
+```json
+[
+ {
+ "id":18,
+ "name":"cluster-1",
+ "domain":"example.com",
+ "created_at":"2019-01-02T20:18:12.563Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"group_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://104.197.68.152",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ }
+ },
+ {
+ "id":19,
+ "name":"cluster-2",
+ ...
+ }
+]
+```
+
+## Get a single group cluster
+
+Gets a single group cluster.
+
+```
+GET /groups/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `cluster_id` | integer | yes | The ID of the cluster |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/groups/26/clusters/18
+```
+
+Example response:
+
+```json
+{
+ "id":18,
+ "name":"cluster-1",
+ "domain":"example.com",
+ "created_at":"2019-01-02T20:18:12.563Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"group_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://104.197.68.152",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ },
+ "group":
+ {
+ "id":26,
+ "name":"group-with-clusters-api",
+ "web_url":"https://gitlab.example.com/group-with-clusters-api"
+ }
+}
+```
+
+## Add existing cluster to group
+
+Adds an existing Kubernetes cluster to the group.
+
+```
+POST /groups/:id/clusters/user
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `name` | String | yes | The name of the cluster |
+| `domain` | String | no | The [base domain](../user/group/clusters/index.md#base-domain) of the cluster |
+| `enabled` | Boolean | no | Determines if cluster is active or not, defaults to true |
+| `managed` | Boolean | no | Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true |
+| `platform_kubernetes_attributes[api_url]` | String | yes | The URL to access the Kubernetes API |
+| `platform_kubernetes_attributes[token]` | String | yes | The token to authenticate against Kubernetes |
+| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
+| `platform_kubernetes_attributes[authorization_type]` | String | no | The cluster authorization type: `rbac`, `abac` or `unknown_authorization`. Defaults to `rbac`. |
+| `environment_scope` | String | no | The associated environment to the cluster. Defaults to `*` **(PREMIUM)** |
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/groups/26/clusters/user \
+-H "Accept: application/json" \
+-H "Content-Type:application/json" \
+--request POST --data '{"name":"cluster-5", "platform_kubernetes_attributes":{"api_url":"https://35.111.51.20","token":"12345","ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"}}'
+```
+
+Example response:
+
+```json
+{
+ "id":24,
+ "name":"cluster-5",
+ "created_at":"2019-01-03T21:53:40.610Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"group_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://35.111.51.20",
+ "authorization_type":"rbac",
+ "ca_cert":"-----BEGIN CERTIFICATE-----\r\nhFiK1L61owwDQYJKoZIhvcNAQELBQAw\r\nLzEtMCsGA1UEAxMkZDA1YzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM4ZDBj\r\nMB4XDTE4MTIyNzIwMDM1MVoXDTIzMTIyNjIxMDM1MVowLzEtMCsGA1UEAxMkZDA1\r\nYzQ1YjctNzdiMS00NDY0LThjNmEtMTQ0ZDJkZjM.......-----END CERTIFICATE-----"
+ },
+ "group":
+ {
+ "id":26,
+ "name":"group-with-clusters-api",
+ "web_url":"https://gitlab.example.com/root/group-with-clusters-api"
+ }
+}
+```
+
+## Edit group cluster
+
+Updates an existing group cluster.
+
+```
+PUT /groups/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `cluster_id` | integer | yes | The ID of the cluster |
+| `name` | String | no | The name of the cluster |
+| `domain` | String | no | The [base domain](../user/group/clusters/index.md#base-domain) of the cluster |
+| `platform_kubernetes_attributes[api_url]` | String | no | The URL to access the Kubernetes API |
+| `platform_kubernetes_attributes[token]` | String | no | The token to authenticate against Kubernetes |
+| `platform_kubernetes_attributes[ca_cert]` | String | no | TLS certificate (needed if API is using a self-signed TLS certificate |
+| `environment_scope` | String | no | The associated environment to the cluster **(PREMIUM)** |
+
+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 cluster to group"](#add-existing-cluster-to-group) endpoint.
+
+Example request:
+
+```bash
+curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/groups/26/clusters/24 \
+-H "Content-Type:application/json" \
+--request PUT --data '{"name":"new-cluster-name","domain":"new-domain.com","api_url":"https://new-api-url.com"}'
+```
+
+Example response:
+
+```json
+{
+ "id":24,
+ "name":"new-cluster-name",
+ "domain":"new-domain.com",
+ "created_at":"2019-01-03T21:53:40.610Z",
+ "provider_type":"user",
+ "platform_type":"kubernetes",
+ "environment_scope":"*",
+ "cluster_type":"group_type",
+ "user":
+ {
+ "id":1,
+ "name":"Administrator",
+ "username":"root",
+ "state":"active",
+ "avatar_url":"https://www.gravatar.com/avatar/4249f4df72b..",
+ "web_url":"https://gitlab.example.com/root"
+ },
+ "platform_kubernetes":
+ {
+ "api_url":"https://new-api-url.com",
+ "authorization_type":"rbac",
+ "ca_cert":null
+ },
+ "group":
+ {
+ "id":26,
+ "name":"group-with-clusters-api",
+ "web_url":"https://gitlab.example.com/group-with-clusters-api"
+ }
+}
+
+```
+
+## Delete group cluster
+
+Deletes an existing group cluster.
+
+```
+DELETE /groups/:id/clusters/:cluster_id
+```
+
+Parameters:
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) |
+| `cluster_id` | integer | yes | The ID of the cluster |
+
+Example request:
+
+```bash
+curl --request DELETE --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/groups/26/clusters/23'
+```
diff --git a/doc/api/groups.md b/doc/api/groups.md
index d05e4b29fef..0d500f783aa 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -634,11 +634,13 @@ Parameters:
By default, groups only get 20 namespaces at a time because the API results are paginated.
To get more (up to 100), pass the following as an argument to the API call:
+
```
/groups?per_page=100
```
And to switch pages add:
+
```
/groups?per_page=100&page=2
```
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 23126a05b66..96a547551f1 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -357,7 +357,6 @@ GET /projects/:id/issues?confidential=true
| `updated_before` | datetime | no | Return issues updated on or before the given time |
| `confidential` | Boolean | no | Filter confidential or public issues. |
-
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues
```
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 1add5f432ac..0e45ee1a583 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -409,10 +409,10 @@ Possible response status codes:
> - The use of `CI_JOB_TOKEN` in the artifacts download API was [introduced][ee-2346]
> in [GitLab Premium][ee] 9.5.
-Download the artifacts zipped archive from the given reference name and job,
-provided the job finished successfully. This is the same as
-[getting the job's artifacts](#get-job-artifacts), but by defining the job's
-name instead of its ID.
+Download the artifacts zipped archive from the latest successful pipeline for
+the given reference name and job, provided the job finished successfully. This
+is the same as [getting the job's artifacts](#get-job-artifacts), but by
+defining the job's name instead of its ID.
```
GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
@@ -506,9 +506,9 @@ Possible response status codes:
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23538) in GitLab 11.5.
-Download a single artifact file from a specific tag or branch from within the
-job's artifacts archive. The file is extracted from the archive and streamed to
-the client.
+Download a single artifact file for a specific job of the latest successful
+pipeline for the given reference name from within the job's artifacts archive.
+The file is extracted from the archive and streamed to the client.
```
GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name
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_requests.md b/doc/api/merge_requests.md
index de87e4a0aee..1ade46efb1c 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -44,7 +44,7 @@ Parameters:
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead. |
| `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me` |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. |
-| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
+| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `source_branch` | string | no | Return merge requests with the given source branch |
| `target_branch` | string | no | Return merge requests with the given target branch |
@@ -206,7 +206,7 @@ Parameters:
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13060] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ |
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced][ce-13060] in GitLab 9.5)_ |
-| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
+| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `source_branch` | string | no | Return merge requests with the given source branch |
| `target_branch` | string | no | Return merge requests with the given target branch |
@@ -358,7 +358,7 @@ Parameters:
| `scope` | string | no | Return merge requests for the given scope: `created_by_me`, `assigned_to_me` or `all`.<br> |
| `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ |
| `assignee_id` | integer | no | Returns merge requests assigned to the given user `id`. `None` returns unassigned merge requests. `Any` returns merge requests with an assignee. _([Introduced][ce-13060] in GitLab 9.5)_ |
-| `approver_ids` **(STARTER)** | Array[integer] | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
+| `approver_ids` **(STARTER)** | integer array | no | Returns merge requests which have specified all the users with the given `id`s as individual approvers. `None` returns merge requests without approvers. `Any` returns merge requests with an approver. |
| `my_reaction_emoji` | string | no | Return merge requests reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `source_branch` | string | no | Return merge requests with the given source branch |
| `target_branch` | string | no | Return merge requests with the given target branch |
@@ -1331,9 +1331,11 @@ If you don't have permissions to accept this merge request - you'll get a `401`
If the merge request is already merged or closed - you get `405` and error message 'Method Not Allowed'
In case the merge request is not set to be merged when the pipeline succeeds, you'll also get a `406` error.
+
```
PUT /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds
```
+
Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
diff --git a/doc/api/projects.md b/doc/api/projects.md
index da05b090699..ba7e28c279b 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -708,11 +708,17 @@ POST /projects
| `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) |
| `default_branch` | string | no | `master` by default |
| `description` | string | no | Short project description |
-| `issues_enabled` | boolean | no | Enable issues for this project |
-| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `jobs_enabled` | boolean | no | Enable jobs for this project |
-| `wiki_enabled` | boolean | no | Enable wiki for this project |
-| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `issues_enabled` | boolean | no | (deprecated) Enable issues for this project. Use `issues_access_level` instead |
+| `merge_requests_enabled` | boolean | no | (deprecated) Enable merge requests for this project. Use `merge_requests_access_level` instead |
+| `jobs_enabled` | boolean | no | (deprecated) Enable jobs for this project. Use `builds_access_level` instead |
+| `wiki_enabled` | boolean | no | (deprecated) Enable wiki for this project. Use `wiki_access_level` instead |
+| `snippets_enabled` | boolean | no | (deprecated) Enable snippets for this project. Use `snippets_access_level` instead |
+| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
@@ -721,13 +727,19 @@ POST /projects
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
-| `merge_method` | string | no | Set the merge method used |
+| `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `build_git_strategy` | string | no | The Git strategy. Defaults to `fetch` |
+| `build_timeout` | integer | no | The maximum amount of time in minutes that a job is able run (in seconds) |
+| `auto_cancel_pending_pipelines` | string | no | Auto-cancel pending pipelines (Note: this is not a boolean, but enabled/disabled |
+| `build_coverage_regex` | string | no | Test coverage parsing |
| `ci_config_path` | string | no | The path to CI config file |
+| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for this project |
+| `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) |
| `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins |
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge requests by default |
| `mirror` | boolean | no | **(STARTER)** Enables pull mirroring in a project |
@@ -753,11 +765,17 @@ POST /projects/user/:user_id
| `path` | string | no | Custom repository name for new project. By default generated based on name |
| `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) |
| `description` | string | no | Short project description |
-| `issues_enabled` | boolean | no | Enable issues for this project |
-| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `jobs_enabled` | boolean | no | Enable jobs for this project |
-| `wiki_enabled` | boolean | no | Enable wiki for this project |
-| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `issues_enabled` | boolean | no | (deprecated) Enable issues for this project. Use `issues_access_level` instead |
+| `merge_requests_enabled` | boolean | no | (deprecated) Enable merge requests for this project. Use `merge_requests_access_level` instead |
+| `jobs_enabled` | boolean | no | (deprecated) Enable jobs for this project. Use `builds_access_level` instead |
+| `wiki_enabled` | boolean | no | (deprecated) Enable wiki for this project. Use `wiki_access_level` instead |
+| `snippets_enabled` | boolean | no | (deprecated) Enable snippets for this project. Use `snippets_access_level` instead |
+| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
@@ -766,16 +784,22 @@ POST /projects/user/:user_id
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
-| `merge_method` | string | no | Set the merge method used |
+| `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `build_git_strategy` | string | no | The Git strategy. Defaults to `fetch` |
+| `build_timeout` | integer | no | The maximum amount of time in minutes that a job is able run (in seconds) |
+| `auto_cancel_pending_pipelines` | string | no | Auto-cancel pending pipelines (Note: this is not a boolean, but enabled/disabled |
+| `build_coverage_regex` | string | no | Test coverage parsing |
| `ci_config_path` | string | no | The path to CI config file |
+| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for this project |
+| `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) |
| `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins |
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge requests by default |
-| `external_authorization_classification_label` | string | no | **(CORE ONLY)** The classification label for the project |
+| `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 |
@@ -798,11 +822,17 @@ PUT /projects/:id
| `path` | string | no | Custom repository name for the project. By default generated based on name |
| `default_branch` | string | no | `master` by default |
| `description` | string | no | Short project description |
-| `issues_enabled` | boolean | no | Enable issues for this project |
-| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `jobs_enabled` | boolean | no | Enable jobs for this project |
-| `wiki_enabled` | boolean | no | Enable wiki for this project |
-| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `issues_enabled` | boolean | no | (deprecated) Enable issues for this project. Use `issues_access_level` instead |
+| `merge_requests_enabled` | boolean | no | (deprecated) Enable merge requests for this project. Use `merge_requests_access_level` instead |
+| `jobs_enabled` | boolean | no | (deprecated) Enable jobs for this project. Use `builds_access_level` instead |
+| `wiki_enabled` | boolean | no | (deprecated) Enable wiki for this project. Use `wiki_access_level` instead |
+| `snippets_enabled` | boolean | no | (deprecated) Enable snippets for this project. Use `snippets_access_level` instead |
+| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
@@ -811,16 +841,22 @@ PUT /projects/:id
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
-| `merge_method` | string | no | Set the merge method used |
+| `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
+| `build_git_strategy` | string | no | The Git strategy. Defaults to `fetch` |
+| `build_timeout` | integer | no | The maximum amount of time in minutes that a job is able run (in seconds) |
+| `auto_cancel_pending_pipelines` | string | no | Auto-cancel pending pipelines (Note: this is not a boolean, but enabled/disabled |
+| `build_coverage_regex` | string | no | Test coverage parsing |
| `ci_config_path` | string | no | The path to CI config file |
| `ci_default_git_depth` | integer | no | Default number of revisions for [shallow cloning](../user/project/pipelines/settings.md#git-shallow-clone) |
+| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for this project |
+| `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) |
| `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins |
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge request by default |
-| `external_authorization_classification_label` | string | no | **(CORE ONLY)** The classification label for the project |
+| `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 |
@@ -1631,6 +1667,7 @@ GET /projects/:id/push_rule
"id": 1,
"project_id": 3,
"commit_message_regex": "Fixes \d+\..*",
+ "commit_message_negative_regex": "ssh\:\/\/",
"branch_name_regex": "",
"deny_delete_tag": false,
"created_at": "2012-10-12T17:04:47Z",
@@ -1663,18 +1700,19 @@ Adds a push rule to a specified project.
POST /projects/:id/push_rule
```
-| Attribute | Type | Required | Description |
-| -------------------------------------- | -------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
-| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
-| `member_check` **(STARTER)** | boolean | no | Restrict commits by author (email) to existing GitLab users |
-| `prevent_secrets` **(STARTER)** | boolean | no | GitLab will reject any files that are likely to contain secrets |
-| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match this, e.g. `Fixed \d+\..*` |
-| `branch_name_regex` **(STARTER)** | string | no | All branch names must match this, e.g. `(feature|hotfix)\/*` |
-| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match this, e.g. `@my-company.com$` |
-| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
-| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
-| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
+| Attribute | Type | Required | Description |
+| --------------------------------------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
+| `member_check` **(STARTER)** | boolean | no | Restrict commits by author (email) to existing GitLab users |
+| `prevent_secrets` **(STARTER)** | boolean | no | GitLab will reject any files that are likely to contain secrets |
+| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match this, e.g. `Fixed \d+\..*` |
+| `commit_message_negative_regex` **(STARTER)** | string | no | No commit message is allowed to match this, e.g. `ssh\:\/\/` |
+| `branch_name_regex` **(STARTER)** | string | no | All branch names must match this, e.g. `(feature|hotfix)\/*` |
+| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match this, e.g. `@my-company.com$` |
+| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
+| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
+| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
### Edit project push rule
@@ -1684,18 +1722,19 @@ Edits a push rule for a specified project.
PUT /projects/:id/push_rule
```
-| Attribute | Type | Required | Description |
-| -------------------------------------- | -------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
-| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
-| `member_check` **(STARTER)** | boolean | no | Restrict commits by author (email) to existing GitLab users |
-| `prevent_secrets` **(STARTER)** | boolean | no | GitLab will reject any files that are likely to contain secrets |
-| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match this, e.g. `Fixed \d+\..*` |
-| `branch_name_regex` **(STARTER)** | string | no | All branch names must match this, e.g. `(feature|hotfix)\/*` |
-| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match this, e.g. `@my-company.com$` |
-| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
-| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
-| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
+| Attribute | Type | Required | Description |
+| --------------------------------------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
+| `member_check` **(STARTER)** | boolean | no | Restrict commits by author (email) to existing GitLab users |
+| `prevent_secrets` **(STARTER)** | boolean | no | GitLab will reject any files that are likely to contain secrets |
+| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match this, e.g. `Fixed \d+\..*` |
+| `commit_message_negative_regex` **(STARTER)** | string | no | No commit message is allowed to match this, e.g. `ssh\:\/\/` |
+| `branch_name_regex` **(STARTER)** | string | no | All branch names must match this, e.g. `(feature|hotfix)\/*` |
+| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match this, e.g. `@my-company.com$` |
+| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
+| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
+| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
### Delete project push rule
diff --git a/doc/api/protected_tags.md b/doc/api/protected_tags.md
index 3adca61a108..fb6fa040244 100644
--- a/doc/api/protected_tags.md
+++ b/doc/api/protected_tags.md
@@ -5,6 +5,7 @@
**Valid access levels**
Currently, these levels are recognized:
+
```
0 => No access
30 => Developer access
diff --git a/doc/api/releases/img/upcoming_release_v12_1.png b/doc/api/releases/img/upcoming_release_v12_1.png
new file mode 100644
index 00000000000..8bd8573ce84
--- /dev/null
+++ b/doc/api/releases/img/upcoming_release_v12_1.png
Binary files differ
diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md
index e7f79a0d359..e74b35fd959 100644
--- a/doc/api/releases/index.md
+++ b/doc/api/releases/index.md
@@ -6,7 +6,7 @@
## List Releases
-Paginated list of Releases, sorted by `created_at`.
+Paginated list of Releases, sorted by `released_at`.
```
GET /projects/:id/releases
@@ -32,6 +32,7 @@ Example response:
"name":"Awesome app v0.2 beta",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eEscape label and milestone titles to prevent XSS in GFM autocomplete. !2740\u003c/li\u003e\n\u003cli\u003ePrevent private snippets from being embeddable.\u003c/li\u003e\n\u003cli\u003eAdd subresources removal to member destroy service.\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:56:19.539Z",
+ "released_at":"2019-01-03T01:56:19.539Z",
"author":{
"id":1,
"name":"Administrator",
@@ -98,6 +99,7 @@ Example response:
"name":"Awesome app v0.1 alpha",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
+ "released_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
@@ -178,6 +180,7 @@ Example response:
"name":"Awesome app v0.1 alpha",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
+ "released_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
@@ -247,6 +250,7 @@ POST /projects/:id/releases
| `assets:links`| array of hash | no | An array of assets links. |
| `assets:links:name`| string | no (if `assets:links` specified, it's required) | The name of the link. |
| `assets:links:url`| string | no (if `assets:links` specified, it's required) | The url of the link. |
+| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
Example request:
@@ -265,6 +269,7 @@ Example response:
"name":"New release",
"description_html":"\u003cp dir=\"auto\"\u003eSuper nice release\u003c/p\u003e",
"created_at":"2019-01-03T02:22:45.118Z",
+ "released_at":"2019-01-03T02:22:45.118Z",
"author":{
"id":1,
"name":"Administrator",
@@ -335,6 +340,7 @@ PUT /projects/:id/releases/:tag_name
| `tag_name` | string | yes | The tag where the release will be created from. |
| `name` | string | no | The release name. |
| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). |
+| `released_at` | datetime | no | The date when the release will be/was ready. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
Example request:
@@ -351,6 +357,7 @@ Example response:
"name":"new name",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
+ "released_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
@@ -430,6 +437,7 @@ Example response:
"name":"new name",
"description_html":"\u003ch2 dir=\"auto\"\u003e\n\u003ca id=\"user-content-changelog\" class=\"anchor\" href=\"#changelog\" aria-hidden=\"true\"\u003e\u003c/a\u003eCHANGELOG\u003c/h2\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eRemove limit of 100 when searching repository code. !8671\u003c/li\u003e\n\u003cli\u003eShow error message when attempting to reopen an MR and there is an open MR for the same branch. !16447 (Akos Gyimesi)\u003c/li\u003e\n\u003cli\u003eFix a bug where internal email pattern wasn't respected. !22516\u003c/li\u003e\n\u003c/ul\u003e",
"created_at":"2019-01-03T01:55:18.203Z",
+ "released_at":"2019-01-03T01:55:18.203Z",
"author":{
"id":1,
"name":"Administrator",
@@ -480,3 +488,11 @@ Example response:
}
}
```
+
+## Upcoming Releases
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38105) in GitLab 12.1.
+
+A release with a `released_at` attribute set to a future date will be labeled an **Upcoming Release** in the UI:
+
+![Upcoming release](img/upcoming_release_v12_1.png)
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index ffae5c17310..4aff79c9c62 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -235,17 +235,17 @@ Example response:
```json
{
- "id": "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863",
- "short_id": "1a0b36b3",
- "title": "Initial commit",
- "created_at": "2014-02-27T08:03:18.000Z",
- "parent_ids": [],
- "message": "Initial commit\n",
- "author_name": "Dmitriy Zaporozhets",
- "author_email": "dmitriy.zaporozhets@gmail.com",
- "authored_date": "2014-02-27T08:03:18.000Z",
- "committer_name": "Dmitriy Zaporozhets",
- "committer_email": "dmitriy.zaporozhets@gmail.com",
- "committed_date": "2014-02-27T08:03:18.000Z"
+ "id": "1a0b36b3cdad1d2ee32457c102a8c0b7056fa863",
+ "short_id": "1a0b36b3",
+ "title": "Initial commit",
+ "created_at": "2014-02-27T08:03:18.000Z",
+ "parent_ids": [],
+ "message": "Initial commit\n",
+ "author_name": "Dmitriy Zaporozhets",
+ "author_email": "dmitriy.zaporozhets@gmail.com",
+ "authored_date": "2014-02-27T08:03:18.000Z",
+ "committer_name": "Dmitriy Zaporozhets",
+ "committer_email": "dmitriy.zaporozhets@gmail.com",
+ "committed_date": "2014-02-27T08:03:18.000Z"
}
```
diff --git a/doc/api/repository_submodules.md b/doc/api/repository_submodules.md
index 2c44c4abc93..5a722a75cb9 100644
--- a/doc/api/repository_submodules.md
+++ b/doc/api/repository_submodules.md
@@ -43,7 +43,7 @@ Example response:
"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"
],
"committed_date": "2018-09-20T09:26:24.000-07:00",
- "authored_date": "2018-09-20T09:26:24.000-07:00",
+ "authored_date": "2018-09-20T09:26:24.000-07:00",
"status": null
}
```
diff --git a/doc/api/system_hooks.md b/doc/api/system_hooks.md
index f8563e819db..1b8c23b8e2d 100644
--- a/doc/api/system_hooks.md
+++ b/doc/api/system_hooks.md
@@ -11,8 +11,6 @@ Read more about [system hooks](../system_hooks/system_hooks.md).
Get a list of all system hooks.
----
-
```
GET /hooks
```
@@ -44,8 +42,6 @@ Example response:
Add a new system hook.
----
-
```
POST /hooks
```
@@ -116,8 +112,6 @@ Example response:
Deletes a system hook.
----
-
```
DELETE /hooks/:id
```
@@ -130,4 +124,4 @@ Example request:
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/hooks/2
-``` \ No newline at end of file
+```
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 3177fec618f..af86ba961f4 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -141,6 +141,7 @@ Parameters:
"message": null
}
```
+
The message will be `null` when creating a lightweight tag otherwise
it will contain the annotation.
diff --git a/doc/api/users.md b/doc/api/users.md
index 884a02dd2bf..b41fd106fc5 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -147,6 +147,21 @@ GET /users
]
```
+Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters.
+
+```json
+[
+ {
+ "id": 1,
+ ...
+ "shared_runners_minutes_limit": 133,
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
+ ...
+ }
+]
+```
+
Users on GitLab [Silver or higher](https://about.gitlab.com/pricing/) will also see
the `group_saml` provider option:
@@ -284,14 +299,15 @@ Example Responses:
```
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
-the `shared_runners_minutes_limit` and `extra_shared_runners_minutes_limit` parameters.
+the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters.
```json
{
"id": 1,
"username": "john_smith",
"shared_runners_minutes_limit": 133,
- "extra_shared_runners_minutes_limit": 133
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
...
}
```
@@ -304,7 +320,8 @@ see the `group_saml` option:
"id": 1,
"username": "john_smith",
"shared_runners_minutes_limit": 133,
- "extra_shared_runners_minutes_limit": 133
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
"identities": [
{"provider": "github", "extern_uid": "2435223452345"},
{"provider": "bitbucket", "extern_uid": "john.smith"},
@@ -332,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
```
@@ -357,9 +377,9 @@ Parameters:
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
- `skip_confirmation` (optional) - Skip confirmation - true or false (default)
-- `external` (optional) - Flags the user as external - true or false(default)
+- `external` (optional) - Flags the user as external - true or false (default)
- `avatar` (optional) - Image file for user's avatar
-- `private_profile` (optional) - User's profile is private - true or false
+- `private_profile` (optional) - User's profile is private - true or false (default)
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)**
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)**
@@ -392,13 +412,14 @@ Parameters:
- `admin` (optional) - User is admin - true or false (default)
- `can_create_group` (optional) - User can create groups - true or false
- `skip_reconfirmation` (optional) - Skip reconfirmation - true or false (default)
-- `external` (optional) - Flags the user as external - true or false(default)
+- `external` (optional) - Flags the user as external - true or false (default)
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user
- `avatar` (optional) - Image file for user's avatar
-- `private_profile` (optional) - User's profile is private - true or false
+- `private_profile` (optional) - User's profile is private - true or false (default)
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)**
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)**
+- `note` (optional) - Admin notes for this user **(STARTER)**
On password update, user will be forced to change it upon next login.
Note, at the moment this method does only return a `404` error,
@@ -593,6 +614,30 @@ Example responses
}
```
+## User counts
+
+Get the counts (same as in top right menu) of the currently signed in user.
+
+| Attribute | Type | Description |
+| --------- | ---- | ----------- |
+| `merge_requests` | number | Merge requests that are active and assigned to current user. |
+
+```
+GET /user_counts
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/user_counts"
+```
+
+Example response:
+
+```json
+{
+ "merge_requests": 4
+}
+```
+
## List user projects
Please refer to the [List of user projects](projects.md#list-user-projects).
diff --git a/doc/api/vulnerabilities.md b/doc/api/vulnerabilities.md
index cc6e5d3960b..eaa4c13de55 100644
--- a/doc/api/vulnerabilities.md
+++ b/doc/api/vulnerabilities.md
@@ -30,6 +30,7 @@ GET /projects/:id/vulnerabilities?scope=all
GET /projects/:id/vulnerabilities?scope=dismissed
GET /projects/:id/vulnerabilities?severity=high
GET /projects/:id/vulnerabilities?confidence=unknown,experimental
+GET /projects/:id/vulnerabilities?pipeline_id=42
```
| Attribute | Type | Required | Description |
@@ -39,6 +40,7 @@ GET /projects/:id/vulnerabilities?confidence=unknown,experimental
| `scope` | string | no | Returns vulnerabilities for the given scope: `all` or `dismissed`. Defaults to `dismissed` |
| `severity` | string array | no | Returns vulnerabilities belonging to specified severity level: `undefined`, `info`, `unknown`, `low`, `medium`, `high`, or `critical`. Defaults to all' |
| `confidence` | string array | no | Returns vulnerabilities belonging to specified confidence level: `undefined`, `ignore`, `unknown`, `experimental`, `low`, `medium`, `high`, or `confirmed`. Defaults to all |
+| `pipeline_id` | integer/string | no | Returns vulnerabilities belonging to specified pipeline. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/vulnerabilities
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 9a5a3624c73..5b2c3a8765c 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.
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index efdcaf5a6f5..9f1ce1fc230 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
@@ -88,9 +96,9 @@ For more information please read [On Docker security: `docker` group considered
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.
+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.
+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:
@@ -115,6 +123,13 @@ In order to do that, follow the steps:
want to use [docker-in-docker] mode, you always have to use `privileged = true`
in your Docker containers.
+ 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].
+
The above command will create a `config.toml` entry similar to this:
```toml
@@ -173,11 +188,6 @@ In order to do that, follow the steps:
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
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 2d7fb323d79..f3896c5232c 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -35,8 +35,8 @@ sudo gitlab-runner register \
--description "docker-ruby-2.1" \
--executor "docker" \
--docker-image ruby:2.1 \
- --docker-postgres latest \
- --docker-mysql latest
+ --docker-services postgres:latest \
+ --docker-services mysql:latest
```
The registered runner will use the `ruby:2.1` Docker image and will run two
@@ -193,13 +193,14 @@ You can simply define an image that will be used for all jobs and a list of
services that you want to use during build time:
```yaml
-image: ruby:2.2
+default:
+ image: ruby:2.2
-services:
- - postgres:9.3
+ services:
+ - postgres:9.3
-before_script:
- - bundle install
+ before_script:
+ - bundle install
test:
script:
@@ -209,8 +210,9 @@ test:
It is also possible to define different images and services per job:
```yaml
-before_script:
- - bundle install
+default:
+ before_script:
+ - bundle install
test:2.1:
image: ruby:2.1
@@ -231,16 +233,53 @@ Or you can pass some [extended configuration options](#extended-docker-configura
for `image` and `services`:
```yaml
+default:
+ image:
+ name: ruby:2.2
+ entrypoint: ["/bin/bash"]
+
+ services:
+ - name: my-postgres:9.4
+ alias: db-postgres
+ entrypoint: ["/usr/local/bin/db-postgres"]
+ command: ["start"]
+
+ before_script:
+ - bundle install
+
+test:
+ script:
+ - bundle exec rake spec
+```
+
+## Passing environment variables to services
+
+You can also pass custom environment [variables](../variables/README.md)
+to fine tune your Docker `images` and `services` directly in the `.gitlab-ci.yml` file.
+For more information, see [custom environment variables](../variables/README.md#gitlab-ciyml-defined-variables)
+
+```yaml
+# The following variables will automatically be passed down to the Postgres container
+# as well as the Ruby container and available within each.
+variables:
+ HTTPS_PROXY: "https://10.1.1.1:8090"
+ HTTP_PROXY: "https://10.1.1.1:8090"
+ POSTGRES_DB: "my_custom_db"
+ POSTGRES_USER: "postgres"
+ POSTGRES_PASSWORD: "example"
+ PGDATA: "/var/lib/postgresql/data"
+ POSTGRES_INITDB_ARGS: "--encoding=UTF8 --data-checksums"
+
+services:
+- name: postgres:9.4
+ alias: db
+ entrypoint: ["docker-entrypoint.sh"]
+ command: ["postgres"]
+
image:
name: ruby:2.2
entrypoint: ["/bin/bash"]
-services:
-- name: my-postgres:9.4
- alias: db-postgres
- entrypoint: ["/usr/local/bin/db-postgres"]
- command: ["start"]
-
before_script:
- bundle install
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 5a302392c54..9295dcfd4e0 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -37,6 +37,7 @@ The following table lists examples with step-by-step tutorials that are containe
| 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). |
+| Parallel testing Ruby & JS | [GitLab CI parallel jobs testing for Ruby & JavaScript projects](https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing). |
### Contributing examples
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index 538843ab8dc..1d4c9221cf2 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -79,10 +79,10 @@ image: java:8
stages:
- build
- deploy
-
+
before_script:
- chmod +x mvnw
-
+
build:
stage: build
script: ./mvnw package
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 d7308a3a5ec..1576efd5a7d 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -452,6 +452,7 @@ To start using Container Registry on our machine, we first need to login to the
```bash
docker login registry.gitlab.com
```
+
Then we can build and push our image to GitLab:
```bash
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index 0b9e9e93e55..1dd3049d53d 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -6,7 +6,7 @@ type: tutorial
This guide covers basic building instructions for PHP projects.
-Two testing scenarios are covered: using the Docker executor and
+Two testing scenarios are covered: using the Docker executor and
using the Shell executor.
## Test PHP projects using the Docker executor
diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md
index 1354a26d6e2..cce33c7a6b4 100644
--- a/doc/ci/git_submodules.md
+++ b/doc/ci/git_submodules.md
@@ -69,12 +69,14 @@ correctly with your CI jobs:
1. Next, if you are using `gitlab-runner` v1.10+, you can set the
`GIT_SUBMODULE_STRATEGY` variable to either `normal` or `recursive` to tell
the runner to fetch your submodules before the job:
- ```yaml
- variables:
- GIT_SUBMODULE_STRATEGY: recursive
- ```
- See the [`.gitlab-ci.yml` reference](yaml/README.md#git-submodule-strategy)
- for more details about `GIT_SUBMODULE_STRATEGY`.
+
+ ```yaml
+ variables:
+ GIT_SUBMODULE_STRATEGY: recursive
+ ```
+
+ See the [`.gitlab-ci.yml` reference](yaml/README.md#git-submodule-strategy)
+ for more details about `GIT_SUBMODULE_STRATEGY`.
1. If you are using an older version of `gitlab-runner`, then use
`git submodule sync/update` in `before_script`:
diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md
index 6c73cbbf8d7..58307660e51 100644
--- a/doc/ci/interactive_web_terminal/index.md
+++ b/doc/ci/interactive_web_terminal/index.md
@@ -8,7 +8,7 @@ type: reference
Interactive web terminals give the user access to a terminal in GitLab for
running one-off commands for their CI pipeline. Since this is giving the user
-shell access to the environment where [GitLab Runner](https://docs.gitlab.com/runner/)
+shell access to the environment where [GitLab Runner](https://docs.gitlab.com/runner/)
is deployed, some [security precautions](../../administration/integration/terminal.md#security) were
taken to protect the users.
@@ -62,4 +62,3 @@ close the terminal window.
## Interactive Web Terminals for the Web IDE **(ULTIMATE ONLY)**
Read the Web IDE docs to learn how to run [Interactive Terminals through the Web IDE](../../user/project/web_ide/index.md).
-
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
index fc89f0fc94f..6401ff90a0a 100644
--- a/doc/ci/introduction/index.md
+++ b/doc/ci/introduction/index.md
@@ -146,14 +146,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 +176,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/merge_request_pipelines/pipelines_for_merged_results/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
index a13857bee25..f63b17a9e5a 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
@@ -42,7 +43,7 @@ Pipelines for merged results require:
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).
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..70916bc0e00
--- /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/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
index 57358434c02..44cbcde264c 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,7 +74,8 @@ 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)
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 960346ac11a..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
@@ -70,7 +77,7 @@ template1=# CREATE USER runner WITH PASSWORD '$password' CREATEDB;
```
*__Note:__ Notice that we created the user with the privilege to be able to
-create databases (`CREATEDB`). In the following steps we will create a database
+create databases (`CREATEDB`). In the following steps we will create a database
explicitly for that user but having that privilege can be useful if in your
testing framework you have tools that drop and create databases.*
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 42dd4f08ed8..4d6ca8cff6d 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -273,6 +273,7 @@ export CI_RUNNER_ID="10"
export CI_RUNNER_DESCRIPTION="my runner"
export CI_RUNNER_TAGS="docker, linux"
export CI_SERVER="yes"
+export CI_SERVER_HOST="example.com"
export CI_SERVER_NAME="GitLab"
export CI_SERVER_REVISION="70606bf"
export CI_SERVER_VERSION="8.9.0"
@@ -644,6 +645,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI_PROJECT_DIR=/builds/gitlab-examples/ci-debug-trace
++ export CI_SERVER=yes
++ CI_SERVER=yes
+++ export 'CI_SERVER_HOST=example.com'
+++ CI_SERVER_HOST='example.com'
++ export 'CI_SERVER_NAME=GitLab CI'
++ CI_SERVER_NAME='GitLab CI'
++ export CI_SERVER_VERSION=
@@ -678,6 +681,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI_JOB_NAME=debug_trace
++ export CI_JOB_STAGE=test
++ CI_JOB_STAGE=test
+++ export CI_SERVER_HOST=example.com
+++ CI_SERVER_HOST=example.com
++ export CI_SERVER_NAME=GitLab
++ CI_SERVER_NAME=GitLab
++ export CI_SERVER_VERSION=8.14.3-ee
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index e911e97d3c8..49543c57886 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -101,6 +101,7 @@ future GitLab releases.**
| `CI_RUNNER_TAGS` | 8.10 | 0.5 | The defined runner tags |
| `CI_RUNNER_VERSION` | all | 10.6 | GitLab Runner version that is executing the current job |
| `CI_SERVER` | all | all | Mark that job is executed in CI environment |
+| `CI_SERVER_HOST` | 12.1 | all | Host component of the GitLab instance URL, without protocol and port (like gitlab.example.com) |
| `CI_SERVER_NAME` | all | all | The name of CI server that is used to coordinate jobs |
| `CI_SERVER_REVISION` | all | all | GitLab revision that is used to schedule jobs |
| `CI_SERVER_VERSION` | all | all | GitLab version that is used to schedule jobs |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 474db05de06..09b9fc87986 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -119,6 +119,35 @@ The following table lists available parameters for jobs:
NOTE: **Note:**
Parameters `types` and `type` are [deprecated](#deprecated-parameters).
+## Setting default parameters
+
+Some parameters can be set globally as the default for all jobs using the
+`default:` keyword. Default parameters can then be overridden by job-specific
+configuration.
+
+The following job parameters can be defined inside a `default:` block:
+
+- [`image`](#image)
+- [`services`](#services)
+- [`before_script`](#before_script-and-after_script)
+- [`after_script`](#before_script-and-after_script)
+- [`cache`](#cache)
+
+In the following example, the `ruby:2.5` image is set as the default for all
+jobs except the `rspec 2.6` job, which uses the `ruby:2.6` image:
+
+```yaml
+default:
+ image: ruby:2.5
+
+rspec:
+ script: bundle exec rspec
+
+rspec 2.6:
+ image: ruby:2.6
+ script: bundle exec rspec
+```
+
## Parameter details
The following are detailed explanations for parameters used to configure CI/CD pipelines.
@@ -239,8 +268,9 @@ It's possible to overwrite the globally defined `before_script` and `after_scrip
if you set it per-job:
```yaml
-before_script:
- - global before script
+default:
+ before_script:
+ - global before script
job:
before_script:
@@ -475,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
@@ -974,7 +1004,7 @@ review_app:
stop_review_app:
stage: deploy
variables:
- GIT_STRATEGY: none
+ GIT_STRATEGY: none
script: make delete-app
when: manual
environment:
@@ -1749,6 +1779,10 @@ test:
parallel: 5
```
+TIP: **Tip:**
+Parallelize tests suites across parallel jobs.
+Different languages have different tools to facilitate this.
+
### `trigger` **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
@@ -2221,10 +2255,10 @@ spinach:
script: rake spinach
```
-It's also possible to use multiple parents for `extends`.
-The algorithm used for merge is "closest scope wins", so keys
-from the last member will always shadow anything defined on other levels.
-For example:
+In GitLab 12.0 and later, it's also possible to use multiple parents for
+`extends`. The algorithm used for merge is "closest scope wins", so
+keys from the last member will always shadow anything defined on other
+levels. For example:
```yaml
.only-important:
@@ -2550,18 +2584,39 @@ You can set it globally or per-job in the [`variables`](#variables) section.
The following parameters are deprecated.
-### `types`
+### Globally-defined `types`
CAUTION: **Deprecated:**
`types` is deprecated, and could be removed in a future release.
Use [`stages`](#stages) instead.
-### `type`
+### Job-defined `type`
CAUTION: **Deprecated:**
`type` is deprecated, and could be removed in one of the future releases.
Use [`stage`](#stage) instead.
+### Globally-defined `image`, `services`, `cache`, `before_script`, `after_script`
+
+Defining `image`, `services`, `cache`, `before_script`, and
+`after_script` globally is deprecated. Support could be removed
+from a future release.
+
+Use [`default:`](#setting-default-parameters) instead. For example:
+
+```yaml
+default:
+ image: ruby:2.5
+ services:
+ - docker:dind
+ cache:
+ paths: [vendor/]
+ before_script:
+ - bundle install --path vendor/
+ after_script:
+ - rm -rf tmp/
+```
+
## Custom build directories
> [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1267) in Gitlab Runner 11.10
@@ -2644,7 +2699,7 @@ variables:
The value of `GIT_CLONE_PATH` is expanded once into
`$CI_BUILDS_DIR/go/src/namespace/project`, and results in failure
-because `$CI_BUILDS_DIR` is not expanded.
+because `$CI_BUILDS_DIR` is not expanded.
## Special YAML features
@@ -2829,7 +2884,8 @@ Alternatively, one can pass the `ci.skip` [Git push option][push-option] if
using Git 2.10 or newer:
```sh
-git push -o ci.skip
+git push --push-option=ci.skip # using git 2.10+
+git push -o ci.skip # using git 2.18+
```
<!-- ## Troubleshooting
diff --git a/doc/development/README.md b/doc/development/README.md
index 1566173992a..ea5d9e10e2c 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -17,12 +17,13 @@ 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)
- [Requesting access to Chatops on GitLab.com](chatops_on_gitlabcom.md#requesting-access) (for GitLabbers)
-## UX and frontend guides
+## UX and Frontend guides
- [GitLab Design System](https://design.gitlab.com/) for building GitLab with existing CSS styles and elements
- [Frontend guidelines](fe_guide/index.md)
@@ -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
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index c83a0427c98..7569ccc04c1 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -424,12 +424,8 @@ Will generate a field called `mergeRequestSetWip` that
### Authorizing resources
-To authorize resources inside a mutation, we can include the
-`Gitlab::Graphql::Authorize::AuthorizeResource` concern in the
-mutation.
-
-This allows us to provide the required abilities on the mutation like
-this:
+To authorize resources inside a mutation, we first provide the required
+ abilities on the mutation like this:
```ruby
module Mutations
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index b645a72567c..091edb6ac40 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -147,7 +147,7 @@ Component statuses are linked to configuration documentation for each component.
| [Redis Exporter](#redis-exporter) | Prometheus endpoint with Redis metrics | [✅][redis-exporter-omnibus] | [✅][redis-exporter-charts] | [✅][redis-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
| [Postgres Exporter](#postgres-exporter) | Prometheus endpoint with PostgreSQL metrics | [✅][postgres-exporter-omnibus] | [✅][postgres-exporter-charts] | [✅][postgres-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
| [PgBouncer Exporter](#pgbouncer-exporter) | Prometheus endpoint with PgBouncer metrics | [⚙][pgbouncer-exporter-omnibus] | [❌][pgbouncer-exporter-charts] | [❌][pgbouncer-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
-| [GitLab Monitor](#gitlab-monitor) | Generates a variety of GitLab metrics | [✅][gitlab-monitor-omnibus] | [✅][gitab-monitor-charts] | [✅][gitab-monitor-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
+| [GitLab Monitor](#gitlab-monitor) | Generates a variety of GitLab metrics | [✅][gitlab-monitor-omnibus] | [✅][gitlab-monitor-charts] | [✅][gitlab-monitor-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
| [Node Exporter](#node-exporter) | Prometheus endpoint with system metrics | [✅][node-exporter-omnibus] | [❌][node-exporter-charts] | [❌][node-exporter-charts] | [✅](https://about.gitlab.com/handbook/engineering/monitoring/) | ❌ | ❌ | CE & EE |
| [Mattermost](#mattermost) | Open-source Slack alternative | [⚙][mattermost-omnibus] | [⤓][mattermost-charts] | [⤓][mattermost-charts] | [⤓](../user/project/integrations/mattermost.md) | ❌ | ❌ | CE & EE |
| [MinIO](#minio) | Object storage service | [⤓][minio-omnibus] | [✅][minio-charts] | [✅][minio-charts] | [✅](https://about.gitlab.com/handbook/engineering/infrastructure/production-architecture/#storage-architecture) | ❌ | [⚙][minio-gdk] | CE & EE |
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 3ed586f07e9..bd07a01e782 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -129,6 +129,7 @@ merge_request:
author:
type:
```
+
If you're working on the GitLab EE repository, the entry will be added to
`ee/changelogs/unreleased/` instead.
diff --git a/doc/development/chaos_endpoints.md b/doc/development/chaos_endpoints.md
index 403a5b21827..eb6dde2d24e 100644
--- a/doc/development/chaos_endpoints.md
+++ b/doc/development/chaos_endpoints.md
@@ -15,23 +15,19 @@ Currently, there are four endpoints for simulating the following conditions:
## Enabling chaos endpoints
-For obvious reasons, these endpoints are not enabled by default. They can be enabled by setting the `GITLAB_ENABLE_CHAOS_ENDPOINTS` environment variable to `1`.
-
-For example, if you're using the [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit) this can be done with the following command:
-
-```bash
-GITLAB_ENABLE_CHAOS_ENDPOINTS=1 gdk run
-```
-
-## Securing the chaos endpoints
+For obvious reasons, these endpoints are not enabled by default on `production`.
+They are enabled by default on **development** environments.
DANGER: **Danger:**
-It is highly recommended that you secure access to the chaos endpoints using a secret token. This is recommended when enabling these endpoints locally and essential when running in a staging or other shared environment. You should not enable them in production unless you absolutely know what you're doing.
+It is required that you secure access to the chaos endpoints using a secret token.
+You should not enable them in production unless you absolutely know what you're doing.
-A secret token can be set through the `GITLAB_CHAOS_SECRET` environment variable. For example, when using the [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit) this can be done with the following command:
+A secret token can be set through the `GITLAB_CHAOS_SECRET` environment variable.
+For example, when using the [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit)
+this can be done with the following command:
```bash
-GITLAB_ENABLE_CHAOS_ENDPOINTS=1 GITLAB_CHAOS_SECRET=secret gdk run
+GITLAB_CHAOS_SECRET=secret gdk run
```
Replace `secret` with your own secret token.
@@ -40,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.
@@ -51,15 +51,18 @@ 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. |
-| `duration_s` | integer | no | Minimum duration, in seconds, that the memory should be retained. Defaults to 30s. |
+| 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'
+curl http://localhost:3000/-/chaos/leakmem?memory_mb=1024&duration_s=10&token=secret
```
## CPU spin
@@ -70,35 +73,66 @@ Depending on your rack server setup, your request may timeout after a predermine
If you're using Unicorn, this is done by killing the worker process.
```
-GET /-/chaos/cpuspin
-GET /-/chaos/cpuspin?duration_s=50
+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 |
+| `async` | boolean | no | Set to true to consume CPU in a Sidekiq background worker process |
```bash
-curl http://localhost:3000/-/chaos/cpuspin?duration_s=60 --header 'X-Chaos-Secret: secret'
+curl http://localhost:3000/-/chaos/cpu_spin?duration_s=60 --header 'X-Chaos-Secret: secret'
+curl http://localhost:3000/-/chaos/cpu_spin?duration_s=60&token=secret
+```
+
+## DB spin
+
+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).
+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 |
+| `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'
+curl http://localhost:3000/-/chaos/db_spin?interval_s=1&duration_s=60&token=secret
```
## Sleep
-This endpoint is similar to the CPU Spin endpoint but simulates off-processor activity, such as network calls to backend services. It will sleep for a given duration.
+This endpoint is similar to the CPU Spin endpoint but simulates off-processor activity, such as network calls to backend services. It will sleep for a given duration_s.
-As with the CPU Spin endpoint, this may lead to your request timing out if duration exceeds the configured limit.
+As with the CPU Spin endpoint, this may lead to your request timing out if duration_s exceeds the configured limit.
```
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'
+curl http://localhost:3000/-/chaos/sleep?duration_s=60&token=secret
```
## Kill
@@ -110,8 +144,14 @@ 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 e60800f1ab7..709cd0c806b 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
@@ -207,9 +208,9 @@ first time.
- Extract unrelated changes and refactorings into future merge requests/issues.
- Seek to understand the reviewer's perspective.
- Try to respond to every comment.
-- The merge request author resolves only the discussions they have fully
- addressed. If there's an open reply, an open discussion, a suggestion,
- a question, or anything else, the discussion should be left to be resolved
+- The merge request author resolves only the threads they have fully
+ addressed. If there's an open reply, an open thread, a suggestion,
+ a question, or anything else, the thread should be left to be resolved
by the reviewer.
- Push commits based on earlier rounds of feedback as isolated commits to the
branch. Do not squash until the branch is ready to merge. Reviewers should be
@@ -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,
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 910f9f4bf7a..79c701d7abf 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -58,9 +58,9 @@ issue is labeled with a subject label corresponding to your expertise.
Subject labels are always all-lowercase.
-## Team labels
+## Team labels
-**Important**: Most of the team labels will be soon deprecated in favor of [Group labels](#group-labels).
+**Important**: Most of the team labels will be soon deprecated in favor of [Group labels](#group-labels).
Team labels specify what team is responsible for this issue.
Assigning a team label makes sure issues get the attention of the appropriate
@@ -98,25 +98,11 @@ indicate if an issue needs backend work, frontend work, or both.
Team labels are always capitalized so that they show up as the first label for
any issue.
-
## Stage labels
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.
@@ -139,44 +125,7 @@ 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.
@@ -192,15 +141,15 @@ can be applied to a single issue. You can find the groups listed in the
The current department labels are:
-* ~UX
-* ~Quality
+- ~UX
+- ~Quality
## Specialization labels
These labels narrow the [specialization](https://about.gitlab.com/company/team/structure/#specialist) on a unit of work.
-* ~frontend
-* ~backend
+- ~frontend
+- ~backend
## Release Scoping labels
@@ -248,10 +197,10 @@ There can be multiple facets of the impact. The below is a guideline.
If a bug seems to fall between two severity labels, assign it to the higher-severity label.
- Example(s) of ~S1
- - Data corruption/loss.
+ - Data corruption/loss.
- Security breach.
- - Unable to create an issue or merge request.
- - Unable to add a comment or discussion to the 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.
- A status widget on the merge request page is not working but information can be seen in the test pipeline page.
@@ -261,7 +210,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
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 3f61ad7cb13..17328762c5b 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -74,10 +74,10 @@ request is as follows:
can be found by running `grep css-class ./app -R`.
1. Be prepared to answer questions and incorporate feedback into your MR with new
commits. Once you have fully addressed a suggestion from a reviewer, click the
- "Resolve discussion" button beneath it to mark it resolved.
- 1. The merge request author resolves only the discussions they have fully addressed.
- If there's an open reply or discussion, a suggestion, a question, or anything else,
- the discussion should be left to be resolved by the reviewer.
+ "Resolve thread" button beneath it to mark it resolved.
+ 1. The merge request author resolves only the threads they have fully addressed.
+ If there's an open reply or thread, a suggestion, a question, or anything else,
+ the thread should be left to be resolved by the reviewer.
1. If your MR touches code that executes shell commands, reads or opens files, or
handles paths to files on disk, make sure it adheres to the
[shell command guidelines](../shell_commands.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
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..1413c2f69fb
--- /dev/null
+++ b/doc/development/database_review.md
@@ -0,0 +1,101 @@
+# 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 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)
+ - For data migrations, establish a time estimate for execution
+- Check post deploy migration
+ - Make sure we can expect post deploy migrations to finish within 1 hour max.
+- Check background migrations
+ - Review queries (for example, make sure batch sizes are fine)
+ - Establish a time estimate for execution
+- 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.
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 418e58b22d5..8ce855594df 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -18,7 +18,7 @@ In addition to this page, the following resources to help craft and contribute d
## Source files and rendered web locations
-Documentation for GitLab Community Edition (CE) and Enterprise Edition (EE), along with GitLab Runner and Omnibus, is published to [docs.gitlab.com](https://docs.gitlab.com). The documentation for CE and EE is also published within the application at `/help` on the domain of the GitLab instance.
+Documentation for GitLab Community Edition (CE) and Enterprise Edition (EE), along with GitLab Runner and Omnibus, is published to [docs.gitlab.com](https://docs.gitlab.com). The documentation for CE and EE is also published within the application at `/help` on the domain of the GitLab instance, though there are [plans](https://gitlab.com/groups/gitlab-org/-/epics/693) to end this practice and instead link out from the GitLab application to docs.gitlab.com URLs.
At `/help`, only content for your current edition and version is included, whereas multiple versions' content is available at docs.gitlab.com.
@@ -43,7 +43,7 @@ Meanwhile, anyone can contribute [documentation improvements](improvement-workfl
## Markdown and styles
-[GitLab docs](https://gitlab.com/gitlab-com/gitlab-docs) uses [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown)
+[GitLab docs](https://gitlab.com/gitlab-org/gitlab-docs) uses [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown)
as its markdown rendering engine. See the [GitLab Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference.
Adhere to the [Documentation Style Guide](styleguide.md). If a style standard is missing, you are welcome to suggest one via a merge request.
@@ -274,8 +274,11 @@ Follow this [method for cherry-picking from CE to EE](../automatic_ce_ee_merge.m
## GitLab `/help`
-Every GitLab instance includes the documentation, which is available from `/help`
-(`http://my-instance.com/help`), e.g., <https://gitlab.com/help>.
+Every GitLab instance includes the documentation, which is available at `/help`
+(`https://gitlab.example.com/help`). For example, <https://gitlab.com/help>.
+
+There are [plans](https://gitlab.com/groups/gitlab-org/-/epics/693) to end this
+practice and instead link out from the GitLab application to docs.gitlab.com URLs.
The documentation available online on docs.gitlab.com is continuously
deployed every hour from the `master` branch of CE, EE, Omnibus, and Runner. Therefore,
@@ -381,7 +384,7 @@ on how the left-side navigation menu is built and updated.
NOTE: **Note:**
To preview your changes to documentation locally, follow this
-[development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development-when-contributing-to-gitlab-documentation) or [these instructions for GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/gitlab_docs.md).
+[development guide](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/README.md#development-when-contributing-to-gitlab-documentation) or [these instructions for GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/gitlab_docs.md).
The live preview is currently enabled for the following projects:
@@ -405,7 +408,7 @@ You will need to push a branch to those repositories, it doesn't work for forks.
The `review-docs-deploy*` job will:
-1. Create a new branch in the [gitlab-docs](https://gitlab.com/gitlab-com/gitlab-docs)
+1. Create a new branch in the [gitlab-docs](https://gitlab.com/gitlab-org/gitlab-docs)
project named after the scheme: `$DOCS_GITLAB_REPO_SUFFIX-$CI_ENVIRONMENT_SLUG`,
where `DOCS_GITLAB_REPO_SUFFIX` is the suffix for each product, e.g, `ce` for
CE, etc.
@@ -461,7 +464,7 @@ If you want to know the in-depth details, here's what's really happening:
1. The preview URL is shown both at the job output and in the merge request
widget. You also get the link to the remote pipeline.
1. In the docs project, the pipeline is created and it
- [skips the test jobs](https://gitlab.com/gitlab-com/gitlab-docs/blob/8d5d5c750c602a835614b02f9db42ead1c4b2f5e/.gitlab-ci.yml#L50-55)
+ [skips the test jobs](https://gitlab.com/gitlab-org/gitlab-docs/blob/8d5d5c750c602a835614b02f9db42ead1c4b2f5e/.gitlab-ci.yml#L50-55)
to lower the build time.
1. Once the docs site is built, the HTML files are uploaded as artifacts.
1. A specific Runner tied only to the docs project, runs the Review App job
@@ -485,15 +488,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-com/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.
@@ -556,15 +561,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
@@ -594,7 +600,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",
@@ -609,11 +615,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/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md
index 20eeebf444f..753a636a779 100644
--- a/doc/development/documentation/site_architecture/global_nav.md
+++ b/doc/development/documentation/site_architecture/global_nav.md
@@ -4,9 +4,9 @@ description: "Learn how GitLab docs' global navigation works and how to add new
# Global navigation
-> - [Introduced](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/362)
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/362)
in GitLab 11.6.
-> - [Updated](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/482) in GitLab 12.1.
+> - [Updated](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/482) in GitLab 12.1.
The global nav adds to the left sidebar the ability to
navigate and explore the contents of GitLab's documentation.
@@ -25,7 +25,7 @@ To add a new doc to the nav, first and foremost, check with the technical writin
Once you get their approval and their guidance in regards to the position on the nav,
read trhough this page to understand how it works, and submit a merge request to the
docs site, adding the doc you wish to include in the nav into the
-[global nav data file](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/content/_data/global-nav.yaml).
+[global nav data file](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/global-nav.yaml).
Don't forget to ask a technical writer to review your changes before merging.
@@ -70,7 +70,7 @@ the data among the nav in containers properly [styled](#css-classes).
### Data file
-The [data file](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/content/_data/global-nav.yaml)
+The [data file](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/content/_data/global-nav.yaml)
is structured in three components: sections, categories, and docs.
#### Sections
@@ -248,9 +248,9 @@ Examples:
### Layout file (logic)
-The [layout](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/layouts/global_nav.html)
+The [layout](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/layouts/global_nav.html)
is fed by the [data file](#data-file), builds the global nav, and is rendered by the
-[default](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/layouts/default.html) layout.
+[default](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/layouts/default.html) layout.
There are three main considerations on the logic built for the nav:
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
index 6dd12b5efa7..1aef0ed855c 100644
--- a/doc/development/documentation/site_architecture/index.md
+++ b/doc/development/documentation/site_architecture/index.md
@@ -4,14 +4,14 @@ description: "Learn how GitLab's documentation website is architectured."
# Documentation site architecture
-Learn how we build and architecture [`gitlab-docs`](https://gitlab.com/gitlab-com/gitlab-docs)
+Learn how we build and architecture [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs)
and deploy it to <https://docs.gitlab.com>.
## Repository
While the source of the documentation content is stored in GitLab's respective product
repositories, the source that is used to build the documentation site _from that content_
-is located at <https://gitlab.com/gitlab-com/gitlab-docs>.
+is located at <https://gitlab.com/gitlab-org/gitlab-docs>.
The following diagram illustrates the relationship between the repositories
from where content is sourced, the `gitlab-docs` project, and the published output.
@@ -43,7 +43,7 @@ from where content is sourced, the `gitlab-docs` project, and the published outp
G --> L
```
-See the [README there](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md)
+See the [README there](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/README.md)
for detailed information.
## Assets
@@ -76,7 +76,7 @@ read through the [global navigation](global_nav.md) doc.
The docs site is deployed to production with GitLab Pages, and previewed in
merge requests with Review Apps.
-The deployment aspects will be soon transferred from the [original document](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md)
+The deployment aspects will be soon transferred from the [original document](https://gitlab.com/gitlab-org/gitlab-docs/blob/master/README.md)
to this page.
<!--
diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md
index fe676efa94d..025a946da0e 100644
--- a/doc/development/documentation/structure.md
+++ b/doc/development/documentation/structure.md
@@ -127,7 +127,7 @@ Notes:
## Help and feedback section
-The "help and feedback" section (introduced by [!319](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/319)) displayed at the end of each document
+The "help and feedback" section (introduced by [!319](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/319)) displayed at the end of each document
can be omitted from the doc by adding a key into the its frontmatter:
```yaml
@@ -142,7 +142,7 @@ you must check with a technical writer before doing so.
### Disqus
We also have integrated the docs site with Disqus (introduced by
-[!151](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/151)),
+[!151](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/151)),
allowing our users to post comments.
To omit only the comments from the feedback section, use the following
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index d9cea0614c3..36ffc02644e 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -44,9 +44,9 @@ Include any media types/sources if the content is relevant to readers. You can f
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/):
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
@@ -340,7 +356,7 @@ For other punctuation rules, please refer to the
links shift too, which eventually leads to dead links. If you think it is
compelling to add numbers in headings, make sure to at least discuss it with
someone in the Merge Request.
-- [Avoid using symbols and special chars](https://gitlab.com/gitlab-com/gitlab-docs/issues/84)
+- [Avoid using symbols and special chars](https://gitlab.com/gitlab-org/gitlab-docs/issues/84)
in headers. Whenever possible, they should be plain and short text.
- Avoid adding things that show ephemeral statuses. For example, if a feature is
considered beta or experimental, put this info in a note, not in the heading.
@@ -488,7 +504,7 @@ You can link any up-to-date video that is useful to the GitLab user.
### Embed videos
-> [Introduced](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/472) in GitLab 12.1.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/472) in GitLab 12.1.
GitLab docs (docs.gitlab.com) support embedded videos.
@@ -801,7 +817,7 @@ GitLab.com Free, and all higher tiers.
### How it works
-Introduced by [!244](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/244),
+Introduced by [!244](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/244),
the special markup `**(STARTER)**` will generate a `span` element to trigger the
badges and tooltips (`<span class="badge-trigger starter">`). When the keyword
"only" is added, the corresponding GitLab.com badge will not be displayed.
diff --git a/doc/development/documentation/workflow.md b/doc/development/documentation/workflow.md
index 0abfe4b82a4..9f488fac7d0 100644
--- a/doc/development/documentation/workflow.md
+++ b/doc/development/documentation/workflow.md
@@ -6,5 +6,5 @@ description: Learn the processes for contributing to GitLab's documentation.
Documentation workflows at GitLab differ depending on the reason for the change:
-- [Documentation process for feature changes](feature-change-workflow.md) - The documentation is being created or updated as part of the development and release of a new or enhanced feature. This process involves the developer of the feature (who includes new/updated documentation files as part of the same merge request containing the feature's code) and also involves the product manager and technical writer who are listed for the feature's [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
+- [Documentation process for feature changes](feature-change-workflow.md) - The documentation is being created or updated as part of the development and release of a new or enhanced feature. This process involves the developer of the feature (who includes new/updated documentation files as part of the same merge request containing the feature's code) and also involves the product manager and technical writer who are listed for the feature's [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
- [Documentation improvement workflow](improvement-workflow.md) - All documentation additions not associated with a feature release. Documentation is being created or updated to improve accuracy, completeness, ease of use, or any reason other than a feature change. Anyone (and everyone) can contribute a merge request for this type of change at any time.
diff --git a/doc/development/fe_guide/design_patterns.md b/doc/development/fe_guide/design_patterns.md
index 0342d16a87c..2f372f783f5 100644
--- a/doc/development/fe_guide/design_patterns.md
+++ b/doc/development/fe_guide/design_patterns.md
@@ -53,6 +53,7 @@ When writing a class that needs to manipulate the DOM guarantee a container opti
This is useful when we need that class to be instantiated more than once in the same page.
Bad:
+
```javascript
class Foo {
constructor() {
@@ -63,6 +64,7 @@ new Foo();
```
Good:
+
```javascript
class Foo {
constructor(opts) {
@@ -72,6 +74,7 @@ class Foo {
new Foo({ container: '.my-element' });
```
+
You can find an example of the above in this [class][container-class-example];
[container-class-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/mini_pipeline_graph_dropdown.js
diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md
index 2f8c79abde1..1c6d895b3ab 100644
--- a/doc/development/fe_guide/droplab/droplab.md
+++ b/doc/development/fe_guide/droplab/droplab.md
@@ -25,6 +25,7 @@ If you do not provide any arguments, it will globally query and instantiate all
<!-- ... -->
<ul>
```
+
```js
const droplab = new DropLab();
droplab.init();
@@ -45,6 +46,7 @@ You can add static list items.
<li>Static value 2</li>
<ul>
```
+
```js
const droplab = new DropLab();
droplab.init();
@@ -62,6 +64,7 @@ a non-global instance of DropLab using the `DropLab.prototype.init` method.
<!-- ... -->
<ul>
```
+
```js
const trigger = document.getElementById('trigger');
const list = document.getElementById('list');
@@ -79,6 +82,7 @@ You can also add hooks to an existing DropLab instance using `DropLab.prototype.
<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a>
<ul id="list" data-dropdown><!-- ... --><ul>
```
+
```js
const droplab = new DropLab();
@@ -109,6 +113,7 @@ for all `data-dynamic` dropdown lists tracked by that DropLab instance.
<li><a href="#" data-id="{{id}}">{{text}}</a></li>
</ul>
```
+
```js
const droplab = new DropLab();
@@ -131,6 +136,7 @@ the data as the second argument and the `id` of the trigger element as the first
<li><a href="#" data-id="{{id}}">{{text}}</a></li>
</ul>
```
+
```js
const droplab = new DropLab();
@@ -160,6 +166,7 @@ dropdown lists, one of which is dynamic.
</ul>
</div>
```
+
```js
const droplab = new DropLab();
@@ -216,6 +223,7 @@ Some plugins require configuration values, the config object can be passed as th
<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a>
<ul id="list" data-dropdown><!-- ... --><ul>
```
+
```js
const droplab = new DropLab();
diff --git a/doc/development/fe_guide/droplab/plugins/ajax.md b/doc/development/fe_guide/droplab/plugins/ajax.md
index b6a883ce6c4..4b76b207d88 100644
--- a/doc/development/fe_guide/droplab/plugins/ajax.md
+++ b/doc/development/fe_guide/droplab/plugins/ajax.md
@@ -17,18 +17,19 @@ Add the `Ajax` object to the plugins array of a `DropLab.prototype.init` or `Dro
<a href="#" id="trigger" data-dropdown-trigger="#list">Toggle</a>
<ul id="list" data-dropdown><!-- ... --><ul>
```
+
```js
- const droplab = new DropLab();
+const droplab = new DropLab();
- const trigger = document.getElementById('trigger');
- const list = document.getElementById('list');
+const trigger = document.getElementById('trigger');
+const list = document.getElementById('list');
- droplab.addHook(trigger, list, [Ajax], {
- Ajax: {
- endpoint: '/some-endpoint',
- method: 'setData',
- },
- });
+droplab.addHook(trigger, list, [Ajax], {
+ Ajax: {
+ endpoint: '/some-endpoint',
+ method: 'setData',
+ },
+});
```
Optionally you can set `loadingTemplate` to a HTML string. This HTML string will
diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md
index 1f188c64fe4..b867394a241 100644
--- a/doc/development/fe_guide/droplab/plugins/filter.md
+++ b/doc/development/fe_guide/droplab/plugins/filter.md
@@ -17,25 +17,26 @@ Add the `Filter` object to the plugins array of a `DropLab.prototype.init` or `D
<li><a href="#" data-id="{{id}}">{{text}}</a></li>
<ul>
```
+
```js
- const droplab = new DropLab();
-
- const trigger = document.getElementById('trigger');
- const list = document.getElementById('list');
-
- droplab.init(trigger, list, [Filter], {
- Filter: {
- template: 'text',
- },
- });
-
- droplab.addData('trigger', [{
- id: 0,
- text: 'Jacob',
- }, {
- id: 1,
- text: 'Jeff',
- }]);
+const droplab = new DropLab();
+
+const trigger = document.getElementById('trigger');
+const list = document.getElementById('list');
+
+droplab.init(trigger, list, [Filter], {
+ Filter: {
+ template: 'text',
+ },
+});
+
+droplab.addData('trigger', [{
+ id: 0,
+ text: 'Jacob',
+}, {
+ id: 1,
+ text: 'Jeff',
+}]);
```
Above, the input string will be compared against the `test` key of the passed data objects.
diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md
index e4050213869..db492da478a 100644
--- a/doc/development/fe_guide/droplab/plugins/input_setter.md
+++ b/doc/development/fe_guide/droplab/plugins/input_setter.md
@@ -22,33 +22,34 @@ You can also set the `InputSetter` config to an array of objects, which will all
<li><a href="#" data-id="{{id}}">{{text}}</a></li>
<ul>
```
+
```js
- const droplab = new DropLab();
-
- const trigger = document.getElementById('trigger');
- const list = document.getElementById('list');
-
- const input = document.getElementById('input');
- const div = document.getElementById('div');
-
- droplab.init(trigger, list, [InputSetter], {
- InputSetter: [{
- input: input,
- valueAttribute: 'data-id',
- } {
- input: div,
- valueAttribute: 'data-id',
- inputAttribute: 'data-selected-id',
- }],
- });
-
- droplab.addData('trigger', [{
- id: 0,
- text: 'Jacob',
- }, {
- id: 1,
- text: 'Jeff',
- }]);
+const droplab = new DropLab();
+
+const trigger = document.getElementById('trigger');
+const list = document.getElementById('list');
+
+const input = document.getElementById('input');
+const div = document.getElementById('div');
+
+droplab.init(trigger, list, [InputSetter], {
+ InputSetter: [{
+ input: input,
+ valueAttribute: 'data-id',
+ } {
+ input: div,
+ valueAttribute: 'data-id',
+ inputAttribute: 'data-selected-id',
+ }],
+});
+
+droplab.addData('trigger', [{
+ id: 0,
+ text: 'Jacob',
+}, {
+ id: 1,
+ text: 'Jeff',
+}]);
```
Above, if the second list item was clicked, it would update the `#input` element
diff --git a/doc/development/fe_guide/event_tracking.md b/doc/development/fe_guide/event_tracking.md
index 6ab3fa4acf3..716f6ad7f92 100644
--- a/doc/development/fe_guide/event_tracking.md
+++ b/doc/development/fe_guide/event_tracking.md
@@ -47,7 +47,7 @@ There's a more convenient solution to this problem. When working with HAML templ
Below is an example of `data-track-*` attributes assigned to a button in HAML:
```ruby
-%button.btn{ data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: "my-template" } }
+%button.btn{ data: { track_label: "template_preview", track_property: "my-template", track_event: "click_button", track_value: "" } }
```
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.
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/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 36d5e4ab96b..6bf6113dd81 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -17,8 +17,6 @@ Working with our frontend assets requires Node (v8.10.0 or greater) and Yarn
For our currently-supported browsers, see our [requirements][requirements].
----
-
## Initiatives
Current high-level frontend goals are listed on [Frontend Epics](https://gitlab.com/groups/gitlab-org/-/epics?label_name%5B%5D=frontend).
@@ -77,8 +75,6 @@ How we use Snowplow to track custom events.
Read the [frontend's FAQ](frontend_faq.md) for common small pieces of helpful information.
----
-
## Style Guides
### [JavaScript Style Guide](style_guide_js.md)
@@ -91,20 +87,14 @@ changes.
Our SCSS conventions which are enforced through [scss-lint][scss-lint].
----
-
## [Performance](performance.md)
Best practices for monitoring and maximizing frontend performance.
----
-
## [Security](security.md)
Frontend security practices.
----
-
## [Accessibility](accessibility.md)
Our accessibility standards and resources.
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index b50159c2b75..18ef754642d 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -548,7 +548,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
<component @click="eventHandler"/>
```
-2. Shorthand `:` is preferable over `v-bind`
+1. Shorthand `:` is preferable over `v-bind`
```javascript
// bad
@@ -558,7 +558,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
<component :class="btn"/>
```
-3. Shorthand `#` is preferable over `v-slot`
+1. Shorthand `#` is preferable over `v-slot`
```javascript
// bad
diff --git a/doc/development/fe_guide/style_guide_scss.md b/doc/development/fe_guide/style_guide_scss.md
index 5220c9eeea3..f895cfd36ac 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`).
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 6c7572352ec..421b7265613 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -34,6 +34,7 @@ new_feature
│ └── new_feature_store.js
├── index.js
```
+
_For consistency purposes, we recommend you to follow the same structure._
Let's look into each of them:
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/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/gitaly.md b/doc/development/gitaly.md
index 5552d5d37b4..2ade59b76ed 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -237,24 +237,23 @@ Here are the steps to gate a new feature in Gitaly behind a feature flag.
1. Create prometheus metrics:
```go
- var findAllTagsRequests = prometheus.NewCounterVec(
- prometheus.CounterOpts{
- Name: "gitaly_find_all_tags_requests_total",
- Help: "Counter of go vs ruby implementation of FindAllTags",
- },
- []string{"implementation"},
- )
+ var findAllTagsRequests = prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "gitaly_find_all_tags_requests_total",
+ Help: "Counter of go vs ruby implementation of FindAllTags",
+ },
+ []string{"implementation"},
)
func init() {
- prometheus.Register(findAllTagsRequests)
+ prometheus.Register(findAllTagsRequests)
}
if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
- findAllTagsRequests.WithLabelValues("go").Inc()
+ findAllTagsRequests.WithLabelValues("go").Inc()
// go implementation
} else {
- findAllTagsRequests.WithLabelValues("ruby").Inc()
+ findAllTagsRequests.WithLabelValues("ruby").Inc()
// ruby implementation
}
```
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index f09339eb3a4..f827d240bf6 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -129,17 +129,50 @@ 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)
+ })
+}
+```
+
### 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/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 35c5b155594..910d7296057 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -106,32 +106,31 @@ are very appreciative of the work done by translators and proofreaders!
1. Contribute translations to GitLab. See instructions for
[translating GitLab](translation.md).
- Translating GitLab is a community effort that requires team work and
- attention to detail. Proofreaders play an important role helping new
- contributors, and ensuring the consistency and quality of translations.
- Your conduct and contributions as a translator should reflect this before
- requesting to be a proofreader.
+ Translating GitLab is a community effort that requires team work and
+ attention to detail. Proofreaders play an important role helping new
+ contributors, and ensuring the consistency and quality of translations.
+ Your conduct and contributions as a translator should reflect this before
+ requesting to be a proofreader.
1. Request proofreader permissions by opening a merge request to add yourself
to the list of proofreaders.
- Open the [proofreader.md source file][proofreader-src] and click **Edit**.
+ Open the [proofreader.md source file][proofreader-src] and click **Edit**.
- Add your language in alphabetical order, and add yourself to the list
- including:
- - name
- - link to your GitLab profile
- - link to your CrowdIn profile
+ Add your language in alphabetical order, and add yourself to the list
+ including:
+ - name
+ - link to your GitLab profile
+ - link to your CrowdIn profile
- In the merge request description, please include links to any projects you
- have previously translated.
+ In the merge request description, please include links to any projects you
+ have previously translated.
1. Your request to become a proofreader will be considered on the merits of
your previous translations by [GitLab team members](https://about.gitlab.com/team/)
or [Core team members](https://about.gitlab.com/core-team/) who are fluent in
the language or current proofreaders.
- When a request is made for the first proofreader for a language and there are no [GitLab team members](https://about.gitlab.com/team/)
- or [Core team members](https://about.gitlab.com/core-team/) who speak the language, we will request links to previous translation work in other communities or projects.
-
+ or [Core team members](https://about.gitlab.com/core-team/) who speak the language, we will request links to previous translation work in other communities or projects.
[proofreader-src]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/i18n/proofreader.md
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/integrations/jira_connect.md b/doc/development/integrations/jira_connect.md
index 9ba3b922fd8..e1350b02262 100644
--- a/doc/development/integrations/jira_connect.md
+++ b/doc/development/integrations/jira_connect.md
@@ -30,9 +30,11 @@ The following are required to install and test the app:
1. In the **From this URL** field, provide a link to the app descriptor. The host and port must point to your GitLab instance.
For example:
+
```
https://xxxx.serveo.net/-/jira_connect/app_descriptor.json
```
+
1. Click **Upload**.
If the install was successful, you should see the **GitLab for Jira** app under **Manage apps**.
diff --git a/doc/development/interacting_components.md b/doc/development/interacting_components.md
new file mode 100644
index 00000000000..74d52d808e2
--- /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/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
index 0e8f5486861..227d03bd86f 100644
--- a/doc/development/new_fe_guide/index.md
+++ b/doc/development/new_fe_guide/index.md
@@ -19,7 +19,6 @@ Learn about all the internal JavaScript modules that make up our frontend.
Style guides to keep our code consistent.
-
## [Tips](tips.md)
Tips from our frontend team to develop more efficiently and effectively.
diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/new_fe_guide/style/javascript.md
index 3019eaa089c..802ebd12d92 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`.
diff --git a/doc/development/new_fe_guide/style/prettier.md b/doc/development/new_fe_guide/style/prettier.md
index 4495f38f262..5f44c640d76 100644
--- a/doc/development/new_fe_guide/style/prettier.md
+++ b/doc/development/new_fe_guide/style/prettier.md
@@ -4,7 +4,7 @@ Our code is automatically formatted with [Prettier](https://prettier.io) to foll
## Editor
-The easiest way to include prettier in your workflow is by setting up your preferred editor (all major editors are supported) accordingly. We suggest setting up prettier to run automatically when each file is saved. Find [here](https://prettier.io/docs/en/editors.html) the best way to set it up in your preferred editor.
+The easiest way to include prettier in your workflow is by setting up your preferred editor (all major editors are supported) accordingly. We suggest setting up prettier to run automatically when each file is saved. Find [here](https://prettier.io/docs/en/editors.html) the best way to set it up in your preferred editor.
Please take care that you only let Prettier format the same file types as the global Yarn script does (.js, .vue, and .scss). In VSCode by example you can easily exclude file formats in your settings file:
@@ -28,6 +28,7 @@ Updates all currently staged files (based on `git diff`) with Prettier and saves
```
yarn prettier-staged
```
+
Checks all currently staged files (based on `git diff`) with Prettier and log which files would need manual updating to the console.
```
diff --git a/doc/development/performance.md b/doc/development/performance.md
index c034f4a344b..14b3f8204d2 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -246,6 +246,7 @@ irb(main):002:0> results.last.attributes.keys
irb(main):003:0> results.where(status: "passed").average(:time).to_s
=> "0.211340155844156"
```
+
These results can also be placed into a PostgreSQL database by setting the
`RSPEC_PROFILING_POSTGRES_URL` variable. This is used to profile the test suite
when running in the CI environment.
@@ -266,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:
@@ -284,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/prometheus_metrics.md b/doc/development/prometheus_metrics.md
index 0511e735843..576601372a3 100644
--- a/doc/development/prometheus_metrics.md
+++ b/doc/development/prometheus_metrics.md
@@ -33,12 +33,10 @@ For example: you might be interested in migrating all dependent data to a differ
class ImportCommonMetrics < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/doc/development/shell_scripting_guide/index.md b/doc/development/shell_scripting_guide/index.md
new file mode 100644
index 00000000000..ae7f2154682
--- /dev/null
+++ b/doc/development/shell_scripting_guide/index.md
@@ -0,0 +1,117 @@
+# 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/testing_guide/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md
index 59eb3ecfd7e..2dc06ba10a5 100644
--- a/doc/development/testing_guide/end_to_end/index.md
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -148,7 +148,7 @@ Once you decided where to put [test environment orchestration scenarios] and
the [GitLab QA orchestrator README][gitlab-qa-readme], and [the already existing
instance-level scenarios][instance-level scenarios].
-Continued reading:
+Continued reading:
- [Quick Start Guide](quick_start_guide.md)
- [Style Guide](style_guide.md)
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 05cb03eb4bd..52957d1a1ab 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.
@@ -92,20 +92,25 @@ end
The `view` DSL method will correspond to the rails View, partial, or vue component that renders the elements.
The `element` DSL method in turn declares an element for which a corresponding
-`qa-element-name-dasherized` CSS class will need to be added to the view file.
+`data-qa-selector=element_name_snaked` data attribute will need to be added to the view file.
You can also define a value (String or Regexp) to match to the actual view
code but **this is deprecated** in favor of the above method for two reasons:
- Consistency: there is only one way to define an element
-- Separation of concerns: QA uses dedicated CSS classes instead of reusing code
+- Separation of concerns: QA uses dedicated `data-qa-*` attributes instead of reusing code
or classes used by other components (e.g. `js-*` classes etc.)
```ruby
view 'app/views/my/view.html.haml' do
- # Implicitly require `.qa-logout-button` CSS class to be present in the view
+
+ ### Good ###
+
+ # Implicitly require the CSS selector `[data-qa-selector="logout_button"]` to be present in the view
element :logout_button
+ ### Bad ###
+
## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
# Require `f.submit "Sign in"` to be present in `my/view.html.haml
element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern
@@ -129,24 +134,39 @@ view 'app/views/my/view.html.haml' do
end
```
-To add these elements to the view, you must change the rails View, partial, or vue component by adding a `qa-element-descriptor` class
+To add these elements to the view, you must change the rails View, partial, or vue component by adding a `data-qa-selector` attribute
for each element defined.
-In our case, `qa-login-field`, `qa-password-field` and `qa-sign-in-button`
+In our case, `data-qa-selector="login_field"`, `data-qa-selector="password_field"` and `data-qa-selector="sign_in_button"`
**app/views/my/view.html.haml**
```haml
-= f.text_field :login, class: "form-control top qa-login-field", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required."
-= f.password_field :password, class: "form-control bottom qa-password-field", required: true, title: "This field is required."
-= f.submit "Sign in", class: "btn btn-success qa-sign-in-button"
+= 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.password_field :password, class: "form-control bottom", required: true, title: "This field is required.", data: { qa_selector: 'password_field' }
+= f.submit "Sign in", class: "btn btn-success", data: { qa_selector: 'sign_in_button' }
```
Things to note:
-- The CSS class must be `kebab-cased` (separated with hyphens "`-`")
+- 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)
+ method of definition over the `.qa-selector` CSS class
+
+
+### `data-qa-selector` vs `.qa-selector`
+
+> Introduced in GitLab 12.1
+
+There are two supported methods of defining elements within a view.
+
+1. `data-qa-selector` attribute
+1. `.qa-selector` class
+
+Any existing `.qa-selector` class should be considered deprecated
+and we should prefer the `data-qa-selector` method of definition.
## Running the test locally
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 064fb0e31dd..14a169dcc1d 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
@@ -101,7 +101,7 @@ it 'replaces an existing label if it has the same key' do
page.find('#content-body').click
page.refresh
- labels_block = page.find('.qa-labels-block')
+ labels_block = page.find(%q([data-qa-selector="labels_block"]))
expect(labels_block).to have_content('animal::dolphin')
expect(labels_block).not_to have_content('animal::fox')
@@ -116,9 +116,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 '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.
@@ -130,7 +130,7 @@ it 'keeps both scoped labels when adding a label with a different key' do
page.find('#content-body').click
page.refresh
- labels_block = page.find('.qa-labels-block')
+ labels_block = page.find(%q([data-qa-selector="labels_block"]))
expect(labels_block).to have_content('animal::fox')
expect(labels_block).to have_content('plant::orchid')
@@ -139,14 +139,14 @@ it 'keeps both scoped labels when adding a label with a different key' do
end
```
-> Note that elements are always located using CSS selectors, and a good practice is to add test-specific selectors (this is called adding testability to the application and we will talk more about it later.) For example, the `labels_block` element uses the selector `.qa-labels-block`, which was added specifically for testing purposes.
+> Note that elements are always located using CSS selectors, and a good practice is to add test-specific selectors (this is called "testability"). For example, the `labels_block` element uses the CSS selector [`data-qa-selector="labels_block"`](page_objects.md#data-qa-selector-vs-qa-selector), which was added specifically for testing purposes.
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.
@@ -168,7 +168,7 @@ end
it 'replaces an existing label if it has the same key' do
select_label_and_refresh @new_label_same_scope
- labels_block = page.find('.qa-labels-block')
+ labels_block = page.find(%q([data-qa-selector="labels_block"]))
expect(labels_block).to have_content(@new_label_same_scope)
expect(labels_block).not_to have_content(@initial_label)
@@ -179,7 +179,7 @@ end
it 'keeps both scoped label when adding a label with a different key' do
select_label_and_refresh @new_label_different_scope
- labels_block = page.find('.qa-labels-block')
+ labels_block = page.find(%q([data-qa-selector="labels_block"]))
expect(labels_blocks).to have_content(@new_label_different_scope)
expect(labels_blocks).to have_content(@initial_label)
@@ -222,7 +222,7 @@ As the pre-conditions for our test suite, the things that needs to happen before
- A project being created with an issue and labels already set;
- The issue page being opened with only one scoped label applied to it.
-> When running end-to-end tests as part of the GitLab's continuous integration process [a license is already set as an environment variable](https://gitlab.com/gitlab-org/gitlab-ee/blob/1a60d926740db10e3b5724713285780a4f470531/qa/qa/ee/strategy.rb#L20). For running tests locally you can set up such license by following the document [what tests can be run?](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-remote-grid-environment-variables), based on the [supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables).
+> When running end-to-end tests as part of the GitLab's continuous integration process [a license is already set as an environment variable](https://gitlab.com/gitlab-org/gitlab-ee/blob/1a60d926740db10e3b5724713285780a4f470531/qa/qa/ee/strategy.rb#L20). For running tests locally you can set up such license by following the document [what tests can be run?](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md), based on the [supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables).
#### Implementation
@@ -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:
@@ -305,7 +305,7 @@ module QA
it 'correctly applies scoped labels depending on if they are from the same or a different scope' do
select_labels_and_refresh [@new_label_same_scope, @new_label_different_scope]
- labels_block = page.all('.qa-labels-block')
+ labels_block = page.all(%q([data-qa-selector="labels_block"]))
expect(labels_block).to have_content(@new_label_same_scope)
expect(labels_block).to have_content(@new_label_different_scope)
@@ -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
@@ -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`
@@ -552,37 +552,36 @@ The `text_of_labels_block` method is a simple method that returns the `:labels_b
#### Updates in the view (*.html.haml) and `dropdowns_helper.rb` files
-Now let's change the view and the `dropdowns_helper` files to add the selectors that relate to the Page Object.
+Now let's change the view and the `dropdowns_helper` files to add the selectors that relate to the [Page Objects].
-In the [app/views/shared/issuable/_sidebar.html.haml](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/views/shared/issuable/_sidebar.html.haml) file, on [line 105 ](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L105), add an extra class `qa-edit-link-labels`.
+In [`app/views/shared/issuable/_sidebar.html.haml:105`](https://gitlab.com/gitlab-org/gitlab-ee/blob/7ca12defc7a965987b162a6ebef302f95dc8867f/app/views/shared/issuable/_sidebar.html.haml#L105), add a `data: { qa_selector: 'edit_link_labels' }` data attribute.
The code should look like this:
```haml
-= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right qa-edit-link-labels'
+= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { qa_selector: 'edit_link_labels' }
```
-In the same file, on [line 121](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L121), add an extra class `.qa-dropdown-menu-labels`.
+In the same file, on [line 121](https://gitlab.com/gitlab-org/gitlab-ee/blob/7ca12defc7a965987b162a6ebef302f95dc8867f/app/views/shared/issuable/_sidebar.html.haml#L121), add a `data: { qa_selector: 'dropdown_menu_labels' }` data attribute.
The code should look like this:
```haml
-.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.qa-dropdown-menu-labels
+.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.dropdown-extended-height{ data: { qa_selector: 'dropdown_menu_labels' } }
```
-In the [`dropdowns_helper.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/helpers/dropdowns_helper.rb) file, on [line 94](https://gitlab.com/gitlab-org/gitlab-ee/blob/99e51a374f2c20bee0989cac802e4b5621f72714/app/helpers/dropdowns_helper.rb#L94), add an extra class `qa-dropdown-input-field`.
+In [`app/helpers/dropdowns_helper.rb:94`](https://gitlab.com/gitlab-org/gitlab-ee/blob/7ca12defc7a965987b162a6ebef302f95dc8867f/app/helpers/dropdowns_helper.rb#L94), add a `data: { qa_selector: 'dropdown_input_field' }` data attribute.
The code should look like this:
```ruby
-filter_output = search_field_tag search_id, nil, class: "dropdown-input-field qa-dropdown-input-field", placeholder: placeholder, autocomplete: 'off'
+filter_output = search_field_tag search_id, nil, class: "dropdown-input-field", placeholder: placeholder, autocomplete: 'off', data: { qa_selector: 'dropdown_input_field' }
```
-> Classes starting with `qa-` are used for testing purposes only, and by defining such classes in the elements we add **testability** in the application.
+> `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 class like `qa-labels-block`, it is transformed into `:labels_block` for usage in the Page Objects. So, `qa-edit-link-labels` is transformed into `:edit_link_labels`, `qa-dropdown-menu-labels` is transformed into `:dropdown_menu_labels`, and `qa-dropdown-input-field` is transformed into `:dropdown_input_field`. Also, 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 `qa-` selectors in the specified views.
-
-> We did not define the `qa-labels-block` class in the `app/views/shared/issuable/_sidebar.html.haml` file because it was already there to be used.
+> 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
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 0272e1810f2..6a888142575 100644
--- a/doc/development/testing_guide/end_to_end/style_guide.md
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -63,17 +63,17 @@ We follow a simple formula roughly based on hungarian notation.
- `_checkbox`
- `_radio`
- `_content`
-
+
*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
**Good**
```ruby
view '...' do
- element :edit_button
+ element :edit_button
element :notes_tab
element :squash_checkbox
element :username_field
@@ -84,16 +84,17 @@ end
**Bad**
```ruby
-view '...' do
+view '...' do
# `_confirmation` should be `_field`. what sort of confirmation? a checkbox confirmation? no real way to disambiguate.
# an appropriate replacement would be `element :password_confirmation_field`
element :password_confirmation
- # `clone_options` is too vague. If it's a dropdown menu, it should be `clone_dropdown`.
+ # `clone_options` is too vague. If it's a dropdown menu, it should be `clone_dropdown`.
# If it's a checkbox, it should be `clone_checkbox`
element :clone_options
-
+
# how is this url being displayed? is it a textbox? a simple span?
+ # If it is content on the page, it should be `ssh_clone_url_content`
element :ssh_clone_url
end
```
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 98df0b5ea7c..ff28c2ea5e2 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -79,6 +79,37 @@ 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`.
+- 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`.
+
+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
@@ -434,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).
@@ -487,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:
@@ -499,29 +530,29 @@ 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();
-
+
// ...
});
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');
-
+
// ...
});
```
-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
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index aadbea1a540..96e8c30a679 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -22,62 +22,44 @@ automated testing means, and what are its principles:
- [Five Factor Testing](https://www.devmynd.com/blog/five-factor-testing): Why do we need tests?
- [Principles of Automated Testing](http://www.lihaoyi.com/post/PrinciplesofAutomatedTesting.html): Levels of testing. Prioritize tests. Cost of tests.
----
-
## [Testing levels](testing_levels.md)
Learn about the different testing levels, and how to decide at what level your
changes should be tested.
----
-
## [Testing best practices](best_practices.md)
Everything you should know about how to write good tests: Test Design, RSpec, FactoryBot,
system tests, parameterized tests etc.
----
-
## [Frontend testing standards and style guidelines](frontend_testing.md)
Everything you should know about how to write good Frontend tests: Karma,
testing promises, stubbing etc.
----
-
## [Flaky tests](flaky_tests.md)
What are flaky tests, the different kind of flaky tests we encountered, and what
we do about them.
----
-
## [GitLab tests in the Continuous Integration (CI) context](ci.md)
How GitLab test suite is run in the CI context: setup, caches, artifacts,
parallelization, monitoring.
----
-
## [Review apps](review_apps.md)
How review apps are set up for GitLab CE/EE and how to use them.
----
-
## [Testing Rake tasks](testing_rake_tasks.md)
Everything you should know about how to test Rake tasks.
----
-
## [End-to-end tests](end_to_end/index.md)
Everything you should know about how to run end-to-end tests using
[GitLab QA][gitlab-qa] testing framework.
----
-
[Return to Development documentation](../README.md)
[RSpec]: https://github.com/rspec/rspec-rails#feature-specs
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index ae40d628717..96761622cfe 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -137,8 +137,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 +196,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 +211,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
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index b7e6844f43a..b8ebbbea9d4 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -139,6 +139,7 @@ pwd
```
clear
```
+
### Sample Git taskflow
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.
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index ccba72f0ef8..2caf7dbbc7a 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -23,18 +23,18 @@ To create a project in GitLab:
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
@@ -60,8 +60,8 @@ 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.
+ - **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).
@@ -83,8 +83,8 @@ 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.
+ - **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).
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 73eaf758923..fed3b1ca595 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -59,10 +59,11 @@ Here's a list of the AWS services we will use, with links to pricing information
- **ElastiCache**: An in-memory cache environment will be used to provide a
High Availability Redis configuration. See the
[Amazon ElastiCache pricing](https://aws.amazon.com/elasticache/pricing/).
-
+
NOTE: **Note:** Please note that while we will be using EBS for storage, we do not recommend using EFS as it may negatively impact GitLab's performance. You can review the [relevant documentation](../../administration/high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
## Creating an IAM EC2 instance role and profile
+
To minimize the permissions of the user, we'll create a new [IAM](https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html)
role with limited access:
@@ -90,7 +91,7 @@ We'll now create a VPC, a virtual networking environment that you'll control:
`10.0.0.0/16`. If you don't require dedicated hardware, you can leave
"Tenancy" as default. Click **Yes, Create** when ready.
- ![Create VPC](img/create_vpc.png)
+ ![Create VPC](img/create_vpc.png)
### Subnets
@@ -107,16 +108,16 @@ RDS instances as well:
for example `gitlab-public-10.0.0.0`, select the VPC we created previously,
and at the IPv4 CIDR block let's give it a 24 subnet `10.0.0.0/24`:
- ![Create subnet](img/create_subnet.png)
+ ![Create subnet](img/create_subnet.png)
1. Follow the same steps to create all subnets:
- | Name tag | Type |Availability Zone | CIDR block |
- | -------- | ---- | ---------------- | ---------- |
- | gitlab-public-10.0.0.0 | public | us-west-2a | 10.0.0.0 |
- | gitlab-private-10.0.1.0 | private | us-west-2a | 10.0.1.0 |
- | gitlab-public-10.0.2.0 | public | us-west-2b | 10.0.2.0 |
- | gitlab-private-10.0.3.0 | private | us-west-2b | 10.0.3.0 |
+ | Name tag | Type |Availability Zone | CIDR block |
+ | -------- | ---- | ---------------- | ---------- |
+ | gitlab-public-10.0.0.0 | public | us-west-2a | 10.0.0.0 |
+ | gitlab-private-10.0.1.0 | private | us-west-2a | 10.0.1.0 |
+ | gitlab-public-10.0.2.0 | public | us-west-2b | 10.0.2.0 |
+ | gitlab-private-10.0.3.0 | private | us-west-2b | 10.0.3.0 |
### Route Table
@@ -139,7 +140,7 @@ create a new one:
1. Select it from the table, and then under the **Actions** dropdown choose
"Attach to VPC".
- ![Create gateway](img/create_gateway.png)
+ ![Create gateway](img/create_gateway.png)
1. Choose `gitlab-vpc` from the list and hit **Attach**.
@@ -154,14 +155,14 @@ it receive traffic from any destination.
as destination. In the target, select the `gitlab-gateway` we created previously.
Hit **Save** once done.
- ![Associate subnet with gateway](img/associate_subnet_gateway.png)
+ ![Associate subnet with gateway](img/associate_subnet_gateway.png)
Next, we must associate the **public** subnets to the route table:
1. Select the **Subnet Associations** tab and hit **Edit**.
1. Check only the public subnet and hit **Save**.
- ![Associate subnet with gateway](img/associate_subnet_gateway_2.png)
+ ![Associate subnet with gateway](img/associate_subnet_gateway_2.png)
---
@@ -178,12 +179,12 @@ The security group is basically the firewall:
Inbound Rules tab. You will need to open the SSH, HTTP, and HTTPS ports. Set
the source to `0.0.0.0/0`.
- ![Create security group](img/create_security_group.png)
+ ![Create security group](img/create_security_group.png)
- TIP: **Tip:**
- Based on best practices, you should allow SSH traffic from only a known
- host or CIDR block. In that case, change the SSH source to be custom and give
- it the IP you want to SSH from.
+ TIP: **Tip:**
+ Based on best practices, you should allow SSH traffic from only a known
+ host or CIDR block. In that case, change the SSH source to be custom and give
+ it the IP you want to SSH from.
1. When done, click **Save**.
@@ -204,7 +205,7 @@ create the actual RDS instance.
we defined them in the [subnets section](#subnets)).
Click **Create** when ready.
- ![RDS Subnet Group](img/rds_subnet_group.png)
+ ![RDS Subnet Group](img/rds_subnet_group.png)
### Creating the database
@@ -214,27 +215,27 @@ Now, it's time to create the database:
1. Select PostgreSQL and click **Next**.
1. Since this is a production server, let's choose "Production". Click **Next**.
1. Let's see the instance specifications:
- 1. Leave the license model as is (`postgresql-license`).
- 1. For the version, select the latest of the 9.6 series (check the
- [database requirements](../../install/requirements.md#postgresql-requirements))
- if there are any updates on this).
- 1. For the size, let's select a `t2.medium` instance.
- 1. Multi-AZ-deployment is recommended as redundancy, so choose "Create
- replica in different zone". Read more at
- [High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html).
- 1. A Provisioned IOPS (SSD) storage type is best suited for HA (though you can
- choose a General Purpose (SSD) to reduce the costs). Read more about it at
- [Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html).
-
-1. The rest of the settings on this page request a DB isntance identifier, username
+ 1. Leave the license model as is (`postgresql-license`).
+ 1. For the version, select the latest of the 9.6 series (check the
+ [database requirements](../../install/requirements.md#postgresql-requirements))
+ if there are any updates on this).
+ 1. For the size, let's select a `t2.medium` instance.
+ 1. Multi-AZ-deployment is recommended as redundancy, so choose "Create
+ replica in different zone". Read more at
+ [High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html).
+ 1. A Provisioned IOPS (SSD) storage type is best suited for HA (though you can
+ choose a General Purpose (SSD) to reduce the costs). Read more about it at
+ [Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html).
+
+1. The rest of the settings on this page request a DB instance identifier, username
and a master password. We've chosen to use `gitlab-db-ha`, `gitlab` and a
very secure password respectively. Keep these in hand for later.
1. Click **Next** to proceed to the advanced settings.
1. Make sure to choose our gitlab VPC, our subnet group, set public accessibility to
**No**, and to leave it to create a new security group. The only additional
- change which will be helpful is the database name for which we can use
- `gitlabhq_production`. At the very bottom, there's an option to enable
- auto updates to minor versions. You may want to turn it off.
+ change which will be helpful is the database name for which we can use
+ `gitlabhq_production`. At the very bottom, there's an option to enable
+ auto updates to minor versions. You may want to turn it off.
1. When done, click **Create database**.
### Installing the `pg_trgm` extension for PostgreSQL
@@ -276,7 +277,7 @@ To set up Redis:
Make sure to select our VPC and its [private subnets](#subnets). Click
**Create** when ready.
- ![ElastiCache subnet](img/ec_subnet.png)
+ ![ElastiCache subnet](img/ec_subnet.png)
1. Select **Redis** on the left menu and click **Create** to create a new
Redis cluster. Depending on your load, you can choose whether to enable
@@ -284,16 +285,16 @@ To set up Redis:
chance to deploy Redis in multi availability zones. In this guide, we chose
not to enable it.
1. In the settings section:
- 1. Give the cluster a name (`gitlab-redis`) and a description.
- 1. For the version, select the latest of `3.2` series (e.g., `3.2.10`).
- 1. Select the node type and the number of replicas.
+ 1. Give the cluster a name (`gitlab-redis`) and a description.
+ 1. For the version, select the latest of `3.2` series (e.g., `3.2.10`).
+ 1. Select the node type and the number of replicas.
1. In the advanced settings section:
1. Select the multi-AZ auto-failover option.
1. Select the subnet group we created previously.
1. Manually select the preferred availability zones, and under "Replica 2"
choose a different zone than the other two.
- ![Redis availability zones](img/ec_az.png)
+ ![Redis availability zones](img/ec_az.png)
1. In the security settings, edit the security groups and choose the
`gitlab-security-group` we had previously created.
@@ -316,11 +317,11 @@ and add a custom TCP rule for port `6379` accessible within itself.
On the EC2 dashboard, look for Load Balancer on the left column:
1. Click the **Create Load Balancer** button.
- 1. Choose the Application Load Balancer.
- 1. Give it a name (`gitlab-loadbalancer`) and set the scheme to "internet-facing".
- 1. In the "Listeners" section, make sure it has HTTP and HTTPS.
- 1. In the "Availability Zones" section, select the `gitlab-vpc` we have created
- and associate the **public subnets**.
+ 1. Choose the Application Load Balancer.
+ 1. Give it a name (`gitlab-loadbalancer`) and set the scheme to "internet-facing".
+ 1. In the "Listeners" section, make sure it has HTTP and HTTPS.
+ 1. In the "Availability Zones" section, select the `gitlab-vpc` we have created
+ and associate the **public subnets**.
1. Click **Configure Security Settings** to go to the next section to
select the TLS certificate. When done, go to the next step.
1. In the "Security Groups" section, create a new one by giving it a name
@@ -355,7 +356,7 @@ Choose the AMI:
where `<version>` the latest version as seen on the
[releases page](https://about.gitlab.com/releases/).
- ![Choose AMI](img/choose_ami.png)
+ ![Choose AMI](img/choose_ami.png)
### Choose an instance type
@@ -504,19 +505,19 @@ The EBS volume will host the Git repositories data:
1. Tell GitLab to store its data in the new directory by editing
`/etc/gitlab/gitlab.rb` with your editor:
- ```ruby
- git_data_dirs({
- "default" => { "path" => "/mnt/gitlab-data" }
- })
- ```
+ ```ruby
+ git_data_dirs({
+ "default" => { "path" => "/mnt/gitlab-data" }
+ })
+ ```
- where `/mnt/gitlab-data` the location where you will store the Git data.
+ where `/mnt/gitlab-data` the location where you will store the Git data.
1. Save the file and reconfigure GitLab:
- ```sh
- sudo gitlab-ctl reconfigure
- ```
+ ```sh
+ sudo gitlab-ctl reconfigure
+ ```
TIP: **Tip:**
If you wish to add more than one data volumes to store the Git repositories,
@@ -549,15 +550,15 @@ After you SSH into the instance, configure the domain name:
1. Open `/etc/gitlab/gitlab.rb` with your preferred editor.
1. Edit the `external_url` value:
- ```ruby
- external_url 'http://example.com'
- ```
+ ```ruby
+ external_url 'http://example.com'
+ ```
1. Reconfigure GitLab:
- ```sh
- sudo gitlab-ctl reconfigure
- ```
+ ```sh
+ sudo gitlab-ctl reconfigure
+ ```
You should now be able to reach GitLab at the URL you defined. To use HTTPS
(recommended), see the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https).
@@ -608,9 +609,9 @@ To back up GitLab:
1. SSH into your instance.
1. Take a backup:
- ```sh
- sudo gitlab-rake gitlab:backup:create
- ```
+ ```sh
+ sudo gitlab-rake gitlab:backup:create
+ ```
### Restoring GitLab from a backup
@@ -626,16 +627,16 @@ released, you can update your GitLab instance:
1. SSH into your instance
1. Take a backup:
- ```sh
- sudo gitlab-rake gitlab:backup:create
- ```
+ ```sh
+ sudo gitlab-rake gitlab:backup:create
+ ```
1. Update the repositories and install GitLab:
- ```sh
- sudo apt update
- sudo apt install gitlab-ee
- ```
+ ```sh
+ sudo apt update
+ sudo apt install gitlab-ee
+ ```
After a few minutes, the new version should be up and running.
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index b1f79893baf..c0e1b0ebbc8 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -67,18 +67,19 @@ The first items we need to configure are the basic settings of the underlying vi
1. Enter a `User name` - e.g. **"gitlab-admin"**
1. Select an `Authentication type`, either **SSH public key** or **Password**:
- > **Note:** if you're unsure which authentication type to use, select **Password**
+ > **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
+ 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)_
- 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
- 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)_
1. Choose the appropriate `Subscription` tier for your Azure account
1. Choose an existing `Resource Group` or create a new one - e.g. **"GitLab-CE-Azure"**
- > **Note:** a "Resource group" is a way to group related resources together for easier administration.
- > We chose "GitLab-CE-Azure", but your resource group can have the same name as your VM.
+ > **Note:** a "Resource group" is a way to group related resources together for easier administration.
+ > We chose "GitLab-CE-Azure", but your resource group can have the same name as your VM.
1. Choose a `Location` - if you're unsure, select the default location
@@ -248,6 +249,7 @@ rules in the list:
![Azure - Inbound security rules - List](img/azure-inbound-sec-rules-list.png)
## Connecting to GitLab
+
Use the domain name you set up earlier (or the public IP address) to visit your new GitLab instance
in your browser. If everything has gone according to plan you should be presented with the
following page, asking you to set a _new_ password for the administrator account automatically
@@ -348,6 +350,7 @@ your VM, you can use the IP address in its place in the following command:
```bash
ssh username@your-azure-domain-name.com
```
+
Provide your password at the prompt to authenticate.
#### SSH from Windows (PuTTY)
@@ -411,12 +414,12 @@ Check out our other [Technical Articles][GitLab-Technical-Articles] or browse th
- [GitLab Community Edition][CE]
- [GitLab Enterprise Edition][EE]
- [Microsoft Azure][Azure]
- - [Azure - Free Account FAQ][Azure-Free-Account-FAQ]
- - [Azure - Marketplace][Azure-Marketplace]
- - [Azure Portal][Azure-Portal]
- - [Azure - Pricing Calculator][Azure-Pricing-Calculator]
- - [Azure - Troubleshoot SSH Connections to an Azure Linux VM][Azure-Troubleshoot-SSH-Connection]
- - [Azure - Properly Shutdown an Azure VM][Azure-Properly-Shutdown-VM]
+ - [Azure - Free Account FAQ][Azure-Free-Account-FAQ]
+ - [Azure - Marketplace][Azure-Marketplace]
+ - [Azure Portal][Azure-Portal]
+ - [Azure - Pricing Calculator][Azure-Pricing-Calculator]
+ - [Azure - Troubleshoot SSH Connections to an Azure Linux VM][Azure-Troubleshoot-SSH-Connection]
+ - [Azure - Properly Shutdown an Azure VM][Azure-Properly-Shutdown-VM]
- [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"
diff --git a/doc/install/digitaloceandocker.md b/doc/install/digitaloceandocker.md
index 63bb941ad47..b6bf7c95527 100644
--- a/doc/install/digitaloceandocker.md
+++ b/doc/install/digitaloceandocker.md
@@ -36,30 +36,30 @@ The rest of the steps are identical for macOS and Linux.
1. Login to Digital Ocean.
1. Generate a new API token at <https://cloud.digitalocean.com/settings/api/tokens>.
- This command will create a new DO droplet called `gitlab-test-env-do` that will act as a docker host.
+ This command will create a new DO droplet called `gitlab-test-env-do` that will act as a docker host.
- NOTE: **Note:**
- 4GB is the minimum requirement for a Docker host that will run more than one GitLab instance.
+ NOTE: **Note:**
+ 4GB is the minimum requirement for a Docker host that will run more than one GitLab instance.
- - RAM: 4GB
- - Name: `gitlab-test-env-do`
- - Driver: `digitalocean`
+ - RAM: 4GB
+ - Name: `gitlab-test-env-do`
+ - Driver: `digitalocean`
1. Set the DO token:
- ```sh
- export DOTOKEN=<your generated token>
- ```
+ ```sh
+ export DOTOKEN=<your generated token>
+ ```
1. Create the machine:
- ```sh
- docker-machine create \
- --driver digitalocean \
- --digitalocean-access-token=$DOTOKEN \
- --digitalocean-size "4gb" \
- gitlab-test-env-do
- ```
+ ```sh
+ docker-machine create \
+ --driver digitalocean \
+ --digitalocean-access-token=$DOTOKEN \
+ --digitalocean-size "4gb" \
+ gitlab-test-env-do
+ ```
Resource: <https://docs.docker.com/machine/drivers/digital-ocean/>.
diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md
index 14bf7012c01..be29bcc7cd7 100644
--- a/doc/install/google_cloud_platform/index.md
+++ b/doc/install/google_cloud_platform/index.md
@@ -30,16 +30,16 @@ To deploy GitLab on GCP you first need to create a virtual machine:
1. Go to <https://console.cloud.google.com/compute/instances> and log in with your Google credentials.
1. Click on **Create**
- ![Search for GitLab](img/launch_vm.png)
+ ![Search for GitLab](img/launch_vm.png)
-1. On the next page, you can select the type of VM as well as the
+1. On the next page, you can select the type of VM as well as the
estimated costs. Provide the name of the instance, desired datacenter, and machine type. Note that GitLab recommends at least 2 vCPU's and 4GB of RAM.
- ![Launch on Compute Engine](img/vm_details.png)
+ ![Launch on Compute Engine](img/vm_details.png)
1. Click **Change** under Boot disk to select the size, type, and desired operating system. GitLab supports a [variety of linux operating systems][req], including Ubuntu and Debian. Click **Select** when finished.
- ![Deploy in progress](img/boot_disk.png)
+ ![Deploy in progress](img/boot_disk.png)
1. As a last step allow HTTP and HTTPS traffic, then click **Create**. The process will finish in a few seconds.
@@ -53,13 +53,13 @@ After a few seconds, the instance will be created and available to log in. The n
1. Click on the SSH button to connect to the instance.
1. A new window will appear, with you logged into the instance.
- ![GitLab first sign in](img/ssh_terminal.png)
+ ![GitLab first sign in](img/ssh_terminal.png)
1. Next, follow the instructions for installing GitLab for the operating system you choose, at <https://about.gitlab.com/install/>. You can use the IP address from the step above, as the hostname.
1. Congratulations! GitLab is now installed and you can access it via your browser. To finish installation, open the URL in your browser and provide the initial administrator password. The username for this account is `root`.
- ![GitLab first sign in](img/first_signin.png)
+ ![GitLab first sign in](img/first_signin.png)
## Next steps
@@ -83,31 +83,31 @@ here's how you configure GitLab to be aware of the change:
1. SSH into the VM. You can easily use the **SSH** button in the Google console
and a new window will pop up.
- ![SSH button](img/vm_created.png)
+ ![SSH button](img/vm_created.png)
- In the future you might want to set up [connecting with an SSH key][ssh]
- instead.
+ In the future you might want to set up [connecting with an SSH key][ssh]
+ instead.
1. Edit the config file of Omnibus GitLab using your favorite text editor:
- ```
- sudo vim /etc/gitlab/gitlab.rb
- ```
+ ```
+ sudo vim /etc/gitlab/gitlab.rb
+ ```
1. Set the `external_url` value to the domain name you wish GitLab to have
**without** `https`:
- ```
- external_url 'http://gitlab.example.com'
- ```
+ ```
+ external_url 'http://gitlab.example.com'
+ ```
- We will set up HTTPS in the next step, no need to do this now.
+ We will set up HTTPS in the next step, no need to do this now.
1. Reconfigure GitLab for the changes to take effect:
- ```
- sudo gitlab-ctl reconfigure
- ```
+ ```
+ sudo gitlab-ctl reconfigure
+ ```
1. You can now visit GitLab using the domain name.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index e9206469e5d..06ec00cecc4 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -299,57 +299,57 @@ use of extensions and concurrent index removal, you need at least PostgreSQL 9.2
1. Install the database packages:
- ```sh
- sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib
- ```
+ ```sh
+ sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib
+ ```
1. Create a database user for GitLab:
- ```sh
- sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
- ```
+ ```sh
+ sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
+ ```
1. Create the `pg_trgm` extension (required for GitLab 8.6+):
- ```sh
- sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
- ```
+ ```sh
+ sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
+ ```
1. Create the GitLab production database and grant all privileges on database:
- ```sh
- sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
- ```
+ ```sh
+ sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
+ ```
1. Try connecting to the new database with the new user:
- ```sh
- sudo -u git -H psql -d gitlabhq_production
- ```
+ ```sh
+ sudo -u git -H psql -d gitlabhq_production
+ ```
1. Check if the `pg_trgm` extension is enabled:
- ```sh
- SELECT true AS enabled
- FROM pg_available_extensions
- WHERE name = 'pg_trgm'
- AND installed_version IS NOT NULL;
- ```
+ ```sh
+ SELECT true AS enabled
+ FROM pg_available_extensions
+ WHERE name = 'pg_trgm'
+ AND installed_version IS NOT NULL;
+ ```
- If the extension is enabled this will produce the following output:
+ If the extension is enabled this will produce the following output:
- ```
- enabled
- ---------
- t
- (1 row)
- ```
+ ```
+ enabled
+ ---------
+ t
+ (1 row)
+ ```
1. Quit the database session:
- ```sh
- gitlabhq_production> \q
- ```
+ ```sh
+ gitlabhq_production> \q
+ ```
## 7. Redis
@@ -831,26 +831,27 @@ how to configure GitLab with a relative URL.
To use GitLab with HTTPS:
1. In `gitlab.yml`:
- 1. Set the `port` option in section 1 to `443`.
- 1. Set the `https` option in section 1 to `true`.
+ 1. Set the `port` option in section 1 to `443`.
+ 1. Set the `https` option in section 1 to `true`.
1. In the `config.yml` of gitlab-shell:
- 1. Set `gitlab_url` option to the HTTPS endpoint of GitLab (e.g. `https://git.example.com`).
- 1. Set the certificates using either the `ca_file` or `ca_path` option.
+ 1. Set `gitlab_url` option to the HTTPS endpoint of GitLab (e.g. `https://git.example.com`).
+ 1. Set the certificates using either the `ca_file` or `ca_path` option.
1. Use the `gitlab-ssl` Nginx example config instead of the `gitlab` config.
- 1. Update `YOUR_SERVER_FQDN`.
- 1. Update `ssl_certificate` and `ssl_certificate_key`.
- 1. Review the configuration file and consider applying other security and performance enhancing features.
+ 1. Update `YOUR_SERVER_FQDN`.
+ 1. Update `ssl_certificate` and `ssl_certificate_key`.
+ 1. Review the configuration file and consider applying other security and performance enhancing features.
Using a self-signed certificate is discouraged but if you must use it, follow the normal directions. Then:
1. Generate a self-signed SSL certificate:
- ```sh
- mkdir -p /etc/nginx/ssl/
- cd /etc/nginx/ssl/
- sudo openssl req -newkey rsa:2048 -x509 -nodes -days 3560 -out gitlab.crt -keyout gitlab.key
- sudo chmod o-r gitlab.key
- ```
+ ```sh
+ mkdir -p /etc/nginx/ssl/
+ cd /etc/nginx/ssl/
+ sudo openssl req -newkey rsa:2048 -x509 -nodes -days 3560 -out gitlab.crt -keyout gitlab.key
+ sudo chmod o-r gitlab.key
+ ```
+
1. In the `config.yml` of gitlab-shell set `self_signed_cert` to `true`.
### Enable Reply by email
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index e4a2d9ecd68..fbbe2a34952 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -70,17 +70,17 @@ In short:
1. Open a terminal and in a new directory run:
- ```sh
- vagrant init openshift/origin-all-in-one
- ```
+ ```sh
+ vagrant init openshift/origin-all-in-one
+ ```
1. This will generate a Vagrantfile based on the all-in-one VM image
1. In the same directory where you generated the Vagrantfile
enter:
- ```sh
- vagrant up
- ```
+ ```sh
+ vagrant up
+ ```
This will download the VirtualBox image and fire up the VM with some preconfigured
values as you can see in the Vagrantfile. As you may have noticed, you need
@@ -195,22 +195,22 @@ In that case, the OpenShift service might not be running, so in order to fix it:
1. SSH into the VM by going to the directory where the Vagrantfile is and then
run:
- ```sh
- vagrant ssh
- ```
+ ```sh
+ vagrant ssh
+ ```
1. Run `systemctl` and verify by the output that the `openshift` service is not
running (it will be in red color). If that's the case start the service with:
- ```sh
- sudo systemctl start openshift
- ```
+ ```sh
+ sudo systemctl start openshift
+ ```
1. Verify the service is up with:
- ```sh
- systemctl status openshift -l
- ```
+ ```sh
+ systemctl status openshift -l
+ ```
Now you will be able to login using `oc` (like we did before) and visit the web
console.
@@ -393,55 +393,55 @@ Let's see how to do that using the following steps.
1. Make sure you are in the `gitlab` project:
- ```sh
- oc project gitlab
- ```
+ ```sh
+ oc project gitlab
+ ```
1. See what services are used for this project:
- ```sh
- oc get svc
- ```
+ ```sh
+ oc get svc
+ ```
- The output will be similar to:
+ The output will be similar to:
- ```
- NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- gitlab-ce 172.30.243.177 <none> 22/TCP,80/TCP 5d
- gitlab-ce-postgresql 172.30.116.75 <none> 5432/TCP 5d
- gitlab-ce-redis 172.30.105.88 <none> 6379/TCP 5d
- ```
+ ```
+ NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+ gitlab-ce 172.30.243.177 <none> 22/TCP,80/TCP 5d
+ gitlab-ce-postgresql 172.30.116.75 <none> 5432/TCP 5d
+ gitlab-ce-redis 172.30.105.88 <none> 6379/TCP 5d
+ ```
1. We need to see the replication controllers of the `gitlab-ce` service.
Get a detailed view of the current ones:
- ```sh
- oc describe rc gitlab-ce
- ```
+ ```sh
+ oc describe rc gitlab-ce
+ ```
- This will return a large detailed list of the current replication controllers.
- Search for the name of the GitLab controller, usually `gitlab-ce-1` or if
- that failed at some point and you spawned another one, it will be named
- `gitlab-ce-2`.
+ This will return a large detailed list of the current replication controllers.
+ Search for the name of the GitLab controller, usually `gitlab-ce-1` or if
+ that failed at some point and you spawned another one, it will be named
+ `gitlab-ce-2`.
1. Scale GitLab using the previous information:
- ```sh
- oc scale --replicas=2 replicationcontrollers gitlab-ce-2
- ```
+ ```sh
+ oc scale --replicas=2 replicationcontrollers gitlab-ce-2
+ ```
1. Get the new replicas number to make sure scaling worked:
- ```sh
- oc get rc gitlab-ce-2
- ```
+ ```sh
+ oc get rc gitlab-ce-2
+ ```
- which will return something like:
+ which will return something like:
- ```
- NAME DESIRED CURRENT AGE
- gitlab-ce-2 2 2 5d
- ```
+ ```
+ NAME DESIRED CURRENT AGE
+ gitlab-ce-2 2 2 5d
+ ```
And that's it! We successfully scaled the replicas to 2 using the CLI.
@@ -478,13 +478,13 @@ For OpenShift v3.0, you will need to do this manually:
1. Edit the Security Context:
- ```sh
- oc edit scc anyuid
- ```
+ ```sh
+ oc edit scc anyuid
+ ```
1. Add `system:serviceaccount:<project>:gitlab-ce-user` to the `users` section.
If you changed the Application Name from the default the user will
- will be `<app-name>-user` instead of `gitlab-ce-user`
+ will be `<app-name>-user` instead of `gitlab-ce-user`
1. Save and exit the editor
diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md
index b53624a33bf..bc6364f57f7 100644
--- a/doc/install/relative_url.md
+++ b/doc/install/relative_url.md
@@ -58,59 +58,59 @@ assumptions are made:
Make sure to follow all steps below:
-1. (Optional) If you run short on resources, you can temporarily free up some
- memory by shutting down the GitLab service with the following command:
+1. (Optional) If you run short on resources, you can temporarily free up some
+ memory by shutting down the GitLab service with the following command:
- ```shell
- sudo service gitlab stop
- ```
+ ```shell
+ sudo service gitlab stop
+ ```
-1. Create `/home/git/gitlab/config/initializers/relative_url.rb`
+1. Create `/home/git/gitlab/config/initializers/relative_url.rb`
- ```shell
- cp /home/git/gitlab/config/initializers/relative_url.rb.sample \
- /home/git/gitlab/config/initializers/relative_url.rb
- ```
+ ```shell
+ cp /home/git/gitlab/config/initializers/relative_url.rb.sample \
+ /home/git/gitlab/config/initializers/relative_url.rb
+ ```
- and change the following line:
+ and change the following line:
- ```ruby
- config.relative_url_root = "/gitlab"
- ```
+ ```ruby
+ config.relative_url_root = "/gitlab"
+ ```
-1. Edit `/home/git/gitlab/config/gitlab.yml` and uncomment/change the
- following line:
+1. Edit `/home/git/gitlab/config/gitlab.yml` and uncomment/change the
+ following line:
- ```yaml
- relative_url_root: /gitlab
- ```
+ ```yaml
+ relative_url_root: /gitlab
+ ```
-1. Edit `/home/git/gitlab/config/unicorn.rb` and uncomment/change the
- following line:
+1. Edit `/home/git/gitlab/config/unicorn.rb` and uncomment/change the
+ following line:
- ```ruby
- ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
- ```
+ ```ruby
+ ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab"
+ ```
-1. Edit `/home/git/gitlab-shell/config.yml` and append the relative path to
- the following line:
+1. Edit `/home/git/gitlab-shell/config.yml` and append the relative path to
+ the following line:
- ```yaml
- gitlab_url: http://127.0.0.1/gitlab
- ```
+ ```yaml
+ gitlab_url: http://127.0.0.1/gitlab
+ ```
-1. Make sure you have copied the supplied init script and the defaults file
- as stated in the [installation guide](installation.md#install-init-script).
- Then, edit `/etc/default/gitlab` and set in `gitlab_workhorse_options` the
- `-authBackend` setting to read like:
+1. Make sure you have copied the supplied init script and the defaults file
+ as stated in the [installation guide](installation.md#install-init-script).
+ Then, edit `/etc/default/gitlab` and set in `gitlab_workhorse_options` the
+ `-authBackend` setting to read like:
- ```shell
- -authBackend http://127.0.0.1:8080/gitlab
- ```
+ ```shell
+ -authBackend http://127.0.0.1:8080/gitlab
+ ```
- **Note:**
- If you are using a custom init script, make sure to edit the above
- gitlab-workhorse setting as needed.
+ **Note:**
+ If you are using a custom init script, make sure to edit the above
+ gitlab-workhorse setting as needed.
1. [Restart GitLab][] for the changes to take effect.
@@ -118,9 +118,9 @@ Make sure to follow all steps below:
To disable the relative URL:
-1. Remove `/home/git/gitlab/config/initializers/relative_url.rb`
+1. Remove `/home/git/gitlab/config/initializers/relative_url.rb`
-1. Follow the same as above starting from 2. and set up the
+1. Follow the same as above starting from 2. and set up the
GitLab URL to one that doesn't contain a relative path.
[omnibus-rel]: https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab "How to set up relative URL in Omnibus GitLab"
diff --git a/doc/integration/auth0.md b/doc/integration/auth0.md
index c67375ede50..5061b863e79 100644
--- a/doc/integration/auth0.md
+++ b/doc/integration/auth0.md
@@ -16,64 +16,64 @@ application.
1. At the top of the Settings screen, you should see your Domain, Client ID and
Client Secret. Take note of these as you'll need to put them in the
configuration file. For example:
- - Domain: `test1234.auth0.com`
- - Client ID: `t6X8L2465bNePWLOvt9yi41i`
- - Client Secret: `KbveM3nqfjwCbrhaUy_gDu2dss8TIlHIdzlyf33pB7dEK5u_NyQdp65O_o02hXs2`
+ - Domain: `test1234.auth0.com`
+ - Client ID: `t6X8L2465bNePWLOvt9yi41i`
+ - Client Secret: `KbveM3nqfjwCbrhaUy_gDu2dss8TIlHIdzlyf33pB7dEK5u_NyQdp65O_o02hXs2`
1. Fill in the Allowed Callback URLs:
- - `http://YOUR_GITLAB_URL/users/auth/auth0/callback` (or)
- - `https://YOUR_GITLAB_URL/users/auth/auth0/callback`
+ - `http://YOUR_GITLAB_URL/users/auth/auth0/callback` (or)
+ - `https://YOUR_GITLAB_URL/users/auth/auth0/callback`
1. Fill in the Allowed Origins (CORS):
- - `http://YOUR_GITLAB_URL` (or)
- - `https://YOUR_GITLAB_URL`
+ - `http://YOUR_GITLAB_URL` (or)
+ - `https://YOUR_GITLAB_URL`
1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For omnibus package:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration)
for initial settings.
1. Add the provider configuration:
- For omnibus package:
-
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "auth0",
- "args" => { client_id: 'YOUR_AUTH0_CLIENT_ID',
- client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
- domain: 'YOUR_AUTH0_DOMAIN',
- scope: 'openid profile email'
- }
- }
- ]
- ```
-
- For installations from source:
-
- ```yaml
- - { name: 'auth0',
- args: {
- client_id: 'YOUR_AUTH0_CLIENT_ID',
- client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
- domain: 'YOUR_AUTH0_DOMAIN',
- scope: 'openid profile email' }
- }
- ```
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "auth0",
+ "args" => { client_id: 'YOUR_AUTH0_CLIENT_ID',
+ client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
+ domain: 'YOUR_AUTH0_DOMAIN',
+ scope: 'openid profile email'
+ }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```yaml
+ - { name: 'auth0',
+ args: {
+ client_id: 'YOUR_AUTH0_CLIENT_ID',
+ client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
+ domain: 'YOUR_AUTH0_DOMAIN',
+ scope: 'openid profile email' }
+ }
+ ```
1. Change `YOUR_AUTH0_CLIENT_ID` to the client ID from the Auth0 Console page
from step 5.
@@ -81,8 +81,8 @@ application.
1. Change `YOUR_AUTH0_CLIENT_SECRET` to the client secret from the Auth0 Console
page from step 5.
-1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. [Reconfigure][] or [restart GitLab][] 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 Auth0 icon below the regular sign in
form. Click the icon to begin the authentication process. Auth0 will ask the
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index 68ec8c4b5c2..5d8f2ebcb8b 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -30,97 +30,97 @@ To enable the Bitbucket OmniAuth provider you must register your application
with Bitbucket.org. Bitbucket will generate an application ID and secret key for
you to use.
-1. Sign in to [Bitbucket.org](https://bitbucket.org).
-1. Navigate to your individual user settings (**Bitbucket settings**) or a team's
- settings (**Manage team**), depending on how you want the application registered.
- It does not matter if the application is registered as an individual or a
- team, that is entirely up to you.
-1. Select **OAuth** in the left menu under "Access Management".
-1. Select **Add consumer**.
-1. Provide the required details:
-
- | Item | Description |
- | :--- | :---------- |
- | **Name** | This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. |
- | **Application description** | Fill this in if you wish. |
- | **Callback URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com/users/auth`. |
- | **URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com`. |
-
- NOTE: Be sure to append `/users/auth` to the end of the callback URL
- to prevent a [OAuth2 convert
- redirect](http://tetraph.com/covert_redirect/) vulnerability.
-
- NOTE: Starting in GitLab 8.15, you MUST specify a callback URL, or you will
- see an "Invalid redirect_uri" message. For more details, see [the
- Bitbucket documentation](https://confluence.atlassian.com/bitbucket/oauth-faq-338365710.html).
-
- And grant at least the following permissions:
-
- ```
- Account: Email, Read
- Projects: Read
- Repositories: Read
- Pull Requests: Read
- Issues: Read
- Wiki: Read and Write
- ```
-
- ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png)
-
-1. Select **Save**.
-1. Select your newly created OAuth consumer and you should now see a Key and
- Secret in the list of OAuth consumers. Keep this page open as you continue
- the configuration.
-
- ![Bitbucket OAuth key](img/bitbucket_oauth_keys.png)
-
-1. On your GitLab server, open the configuration file:
-
- ```
- # For Omnibus packages
- sudo editor /etc/gitlab/gitlab.rb
-
- # For installations from source
- sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
- ```
-
-1. Add the Bitbucket provider configuration:
-
- For Omnibus packages:
-
- ```ruby
- gitlab_rails['omniauth_enabled'] = true
-
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "bitbucket",
- "app_id" => "BITBUCKET_APP_KEY",
- "app_secret" => "BITBUCKET_APP_SECRET",
- "url" => "https://bitbucket.org/"
- }
- ]
- ```
-
- For installations from source:
-
- ```yaml
- omniauth:
- enabled: true
- providers:
- - { name: 'bitbucket',
- app_id: 'BITBUCKET_APP_KEY',
- app_secret: 'BITBUCKET_APP_SECRET',
- url: 'https://bitbucket.org/' }
- ```
-
- ---
-
- Where `BITBUCKET_APP_KEY` is the Key and `BITBUCKET_APP_SECRET` the Secret
- from the Bitbucket application page.
-
-1. Save the configuration file.
-1. For the changes to take effect, [reconfigure GitLab][] if you installed via
- Omnibus, or [restart][] if installed from source.
+1. Sign in to [Bitbucket.org](https://bitbucket.org).
+1. Navigate to your individual user settings (**Bitbucket settings**) or a team's
+ settings (**Manage team**), depending on how you want the application registered.
+ It does not matter if the application is registered as an individual or a
+ team, that is entirely up to you.
+1. Select **OAuth** in the left menu under "Access Management".
+1. Select **Add consumer**.
+1. Provide the required details:
+
+ | Item | Description |
+ | :--- | :---------- |
+ | **Name** | This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive. |
+ | **Application description** | Fill this in if you wish. |
+ | **Callback URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com/users/auth`. |
+ | **URL** | The URL to your GitLab installation, e.g., `https://gitlab.example.com`. |
+
+ NOTE: Be sure to append `/users/auth` to the end of the callback URL
+ to prevent a [OAuth2 convert
+ redirect](http://tetraph.com/covert_redirect/) vulnerability.
+
+ NOTE: Starting in GitLab 8.15, you MUST specify a callback URL, or you will
+ see an "Invalid redirect_uri" message. For more details, see [the
+ Bitbucket documentation](https://confluence.atlassian.com/bitbucket/oauth-faq-338365710.html).
+
+ And grant at least the following permissions:
+
+ ```
+ Account: Email, Read
+ Projects: Read
+ Repositories: Read
+ Pull Requests: Read
+ Issues: Read
+ Wiki: Read and Write
+ ```
+
+ ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png)
+
+1. Select **Save**.
+1. Select your newly created OAuth consumer and you should now see a Key and
+ Secret in the list of OAuth consumers. Keep this page open as you continue
+ the configuration.
+
+ ![Bitbucket OAuth key](img/bitbucket_oauth_keys.png)
+
+1. On your GitLab server, open the configuration file:
+
+ ```
+ # For Omnibus packages
+ sudo editor /etc/gitlab/gitlab.rb
+
+ # For installations from source
+ sudo -u git -H editor /home/git/gitlab/config/gitlab.yml
+ ```
+
+1. Add the Bitbucket provider configuration:
+
+ For Omnibus packages:
+
+ ```ruby
+ gitlab_rails['omniauth_enabled'] = true
+
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "bitbucket",
+ "app_id" => "BITBUCKET_APP_KEY",
+ "app_secret" => "BITBUCKET_APP_SECRET",
+ "url" => "https://bitbucket.org/"
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```yaml
+ omniauth:
+ enabled: true
+ providers:
+ - { name: 'bitbucket',
+ app_id: 'BITBUCKET_APP_KEY',
+ app_secret: 'BITBUCKET_APP_SECRET',
+ url: 'https://bitbucket.org/' }
+ ```
+
+ ---
+
+ Where `BITBUCKET_APP_KEY` is the Key and `BITBUCKET_APP_SECRET` the Secret
+ from the Bitbucket application page.
+
+1. Save the configuration file.
+1. For the changes to take effect, [reconfigure GitLab][] if you installed via
+ Omnibus, or [restart][] if installed from source.
On the sign in page there should now be a Bitbucket icon below the regular sign
in form. Click the icon to begin the authentication process. Bitbucket will ask
diff --git a/doc/integration/cas.md b/doc/integration/cas.md
index c6178fa44f0..f99337376a8 100644
--- a/doc/integration/cas.md
+++ b/doc/integration/cas.md
@@ -2,63 +2,63 @@
To enable the CAS OmniAuth provider you must register your application with your CAS instance. This requires the service URL GitLab will supply to CAS. It should be something like: `https://gitlab.example.com:443/users/auth/cas3/callback?url`. By default handling for SLO is enabled, you only need to configure CAS for backchannel logout.
-1. On your GitLab server, open the configuration file.
+1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For omnibus package:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- cd /home/git/gitlab
+ ```sh
+ cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ sudo -u git -H editor config/gitlab.yml
+ ```
-1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Add the provider configuration:
+1. Add the provider configuration:
- For omnibus package:
+ For omnibus package:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name"=> "cas3",
- "label"=> "cas",
- "args"=> {
- "url"=> 'CAS_SERVER',
- "login_url"=> '/CAS_PATH/login',
- "service_validate_url"=> '/CAS_PATH/p3/serviceValidate',
- "logout_url"=> '/CAS_PATH/logout'
- }
- }
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name"=> "cas3",
+ "label"=> "cas",
+ "args"=> {
+ "url"=> 'CAS_SERVER',
+ "login_url"=> '/CAS_PATH/login',
+ "service_validate_url"=> '/CAS_PATH/p3/serviceValidate',
+ "logout_url"=> '/CAS_PATH/logout'
+ }
+ }
+ ]
+ ```
- For installations from source:
+ For installations from source:
- ```
- - { name: 'cas3',
- label: 'cas',
- args: {
- url: 'CAS_SERVER',
- login_url: '/CAS_PATH/login',
- service_validate_url: '/CAS_PATH/p3/serviceValidate',
- logout_url: '/CAS_PATH/logout'} }
- ```
+ ```
+ - { name: 'cas3',
+ label: 'cas',
+ args: {
+ url: 'CAS_SERVER',
+ login_url: '/CAS_PATH/login',
+ service_validate_url: '/CAS_PATH/p3/serviceValidate',
+ logout_url: '/CAS_PATH/logout'} }
+ ```
-1. Change 'CAS_PATH' to the root of your CAS instance (ie. `cas`).
+1. Change 'CAS_PATH' to the root of your CAS instance (ie. `cas`).
-1. If your CAS instance does not use default TGC lifetimes, update the `cas3.session_duration` to at least the current TGC maximum lifetime. To explicitly disable SLO, regardless of CAS settings, set this to 0.
+1. If your CAS instance does not use default TGC lifetimes, update the `cas3.session_duration` to at least the current TGC maximum lifetime. To explicitly disable SLO, regardless of CAS settings, set this to 0.
-1. Save the configuration file.
+1. Save the configuration file.
-1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. [Reconfigure][] or [restart GitLab][] 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 a CAS tab in the sign in form.
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index da1df07a75d..626bd259ed6 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -12,6 +12,7 @@ special searches:
- [Advanced Syntax Search](../user/search/advanced_search_syntax.md)
## Version Requirements
+
<!-- Please remember to update ee/lib/system_check/app/elasticsearch_check.rb if this changes -->
| GitLab version | Elasticsearch version |
@@ -41,7 +42,11 @@ use the packages that are available for your OS.
In order to improve elasticsearch indexing performance, GitLab has made available a [new indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
This will replace the included Ruby indexer in the future but should be considered beta software for now, so there may be some bugs.
-If you would like to use it, please follow the instructions below.
+The Elasticsearch Go indexer is included in Omnibus for GitLab 11.8 and newer.
+
+To use the new Elasticsearch indexer included in Omnibus, check the box "Use the new repository indexer (beta)" when [enabling the Elasticsearch integration](#enabling-elasticsearch).
+
+If you would like to use the Elasticsearch Go indexer with a source installation or an older version of GitLab, please follow the instructions below.
### Installation
@@ -349,6 +354,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
@@ -424,91 +431,94 @@ Here are some common pitfalls and how to overcome them:
- **How can I verify my GitLab instance is using Elasticsearch?**
- The easiest method is via the rails console (`sudo gitlab-rails console`) by running the following:
+ The easiest method is via the rails console (`sudo gitlab-rails console`) by running the following:
- ```ruby
- u = User.find_by_username('your-username')
- s = SearchService.new(u, {:search => 'search_term'})
- pp s.search_objects.class.name
- ```
+ ```ruby
+ u = User.find_by_username('your-username')
+ s = SearchService.new(u, {:search => 'search_term'})
+ pp s.search_objects.class.name
+ ```
- If you see `Elasticsearch::Model::Response::Records`, you are using Elasticsearch.
+ If you see `Elasticsearch::Model::Response::Records`, you are using Elasticsearch.
- **I updated GitLab and now I can't find anything**
- We continuously make updates to our indexing strategies and aim to support
- newer versions of Elasticsearch. When indexing changes are made, it may
- be necessary for you to [reindex](#adding-gitlabs-data-to-the-elasticsearch-index) after updating GitLab.
+ We continuously make updates to our indexing strategies and aim to support
+ newer versions of Elasticsearch. When indexing changes are made, it may
+ be necessary for you to [reindex](#adding-gitlabs-data-to-the-elasticsearch-index) after updating GitLab.
- **I indexed all the repositories but I can't find anything**
- Make sure you indexed all the database data [as stated above](#adding-gitlabs-data-to-the-elasticsearch-index).
+ Make sure you indexed all the database data [as stated above](#adding-gitlabs-data-to-the-elasticsearch-index).
- Beyond that, check via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) to see if the data shows up on the Elasticsearch side.
+ Beyond that, check via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) to see if the data shows up on the Elasticsearch side.
- If it shows up via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html), check that it shows up via the rails console (`sudo gitlab-rails console`):
+ If it shows up via the [Elasticsearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html), check that it shows up via the rails console (`sudo gitlab-rails console`):
- ```ruby
- u = User.find_by_username('your-username')
- s = SearchService.new(u, {:search => 'search_term', :scope => ‘blobs’})
- pp s.search_objects.to_a
- ```
+ ```ruby
+ u = User.find_by_username('your-username')
+ s = SearchService.new(u, {:search => 'search_term', :scope => ‘blobs’})
+ pp s.search_objects.to_a
+ ```
- See [Elasticsearch Index Scopes](elasticsearch.md#elasticsearch-index-scopes) for more information on searching for specific types of data.
+ See [Elasticsearch Index Scopes](elasticsearch.md#elasticsearch-index-scopes) for more information on searching for specific types of data.
- **I indexed all the repositories but then switched Elasticsearch servers and now I can't find anything**
- You will need to re-run all the rake tasks to re-index the database, repositories, and wikis.
+ You will need to re-run all the rake tasks to re-index the database, repositories, and wikis.
- **The indexing process is taking a very long time**
- The more data present in your GitLab instance, the longer the indexing process takes.
+ 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
- happen that an error during the process causes one or multiple projects to remain locked. In order to unlock them,
- run the `gitlab:elastic:clear_locked_projects` rake task.
+ When performing the initial indexing of blobs, we lock all projects until the project finishes indexing. It could
+ happen that an error during the process causes one or multiple projects to remain locked. In order to unlock them,
+ run the `gitlab:elastic:clear_locked_projects` rake task.
- **"Can't specify parent if no parent field has been configured"**
- If you enabled Elasticsearch before GitLab 8.12 and have not rebuilt indexes you will get
- exception in lots of different cases:
-
- ```text
- Elasticsearch::Transport::Transport::Errors::BadRequest([400] {
- "error": {
- "root_cause": [{
- "type": "illegal_argument_exception",
- "reason": "Can't specify parent if no parent field has been configured"
- }],
- "type": "illegal_argument_exception",
- "reason": "Can't specify parent if no parent field has been configured"
- },
- "status": 400
- }):
- ```
-
- This is because we changed the index mapping in GitLab 8.12 and the old indexes should be removed and built from scratch again,
- see details in the [8-11-to-8-12 update guide](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/8.11-to-8.12.md#11-elasticsearch-index-update-if-you-currently-use-elasticsearch).
+ If you enabled Elasticsearch before GitLab 8.12 and have not rebuilt indexes you will get
+ exception in lots of different cases:
+
+ ```text
+ Elasticsearch::Transport::Transport::Errors::BadRequest([400] {
+ "error": {
+ "root_cause": [{
+ "type": "illegal_argument_exception",
+ "reason": "Can't specify parent if no parent field has been configured"
+ }],
+ "type": "illegal_argument_exception",
+ "reason": "Can't specify parent if no parent field has been configured"
+ },
+ "status": 400
+ }):
+ ```
+
+ This is because we changed the index mapping in GitLab 8.12 and the old indexes should be removed and built from scratch again,
+ see details in the [8-11-to-8-12 update guide](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/update/8.11-to-8.12.md#11-elasticsearch-index-update-if-you-currently-use-elasticsearch).
- Exception `Elasticsearch::Transport::Transport::Errors::BadRequest`
- If you have this exception (just like in the case above but the actual message is different) please check if you have the correct Elasticsearch version and you met the other [requirements](#system-requirements).
- There is also an easy way to check it automatically with `sudo gitlab-rake gitlab:check` command.
+ If you have this exception (just like in the case above but the actual message is different) please check if you have the correct Elasticsearch version and you met the other [requirements](#system-requirements).
+ There is also an easy way to check it automatically with `sudo gitlab-rake gitlab:check` command.
- Exception `Elasticsearch::Transport::Transport::Errors::RequestEntityTooLarge`
- ```text
- [413] {"Message":"Request size exceeded 10485760 bytes"}
- ```
-
- This exception is seen when your Elasticsearch cluster is configured to reject
- requests above a certain size (10MiB in this case). This corresponds to the
- `http.max_content_length` setting in `elasticsearch.yml`. Increase it to a
- larger size and restart your Elasticsearch cluster.
+ ```text
+ [413] {"Message":"Request size exceeded 10485760 bytes"}
+ ```
- AWS has [fixed limits](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-limits.html)
- for this setting ("Maximum Size of HTTP Request Payloads"), based on the size of
- the underlying instance.
+ This exception is seen when your Elasticsearch cluster is configured to reject
+ requests above a certain size (10MiB in this case). This corresponds to the
+ `http.max_content_length` setting in `elasticsearch.yml`. Increase it to a
+ larger size and restart your Elasticsearch cluster.
+ AWS has [fixed limits](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/aes-limits.html)
+ for this setting ("Maximum Size of HTTP Request Payloads"), based on the size of
+ the underlying instance.
diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md
index fe789a80eed..837434da737 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -2,7 +2,7 @@
To enable the Facebook OmniAuth provider you must register your application with Facebook. Facebook will generate an app ID and secret key for you to use.
-1. Sign in to the [Facebook Developer Platform](https://developers.facebook.com/).
+1. Sign in to the [Facebook Developer Platform](https://developers.facebook.com/).
1. Choose "My Apps" &gt; "Add a New App"
@@ -47,53 +47,53 @@ To enable the Facebook OmniAuth provider you must register your application with
![Facebook API Keys](img/facebook_api_keys.png)
-1. On your GitLab server, open the configuration file.
+1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For omnibus package:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- cd /home/git/gitlab
+ ```sh
+ cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ sudo -u git -H editor config/gitlab.yml
+ ```
-1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Add the provider configuration:
+1. Add the provider configuration:
- For omnibus package:
+ For omnibus package:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "facebook",
- "app_id" => "YOUR_APP_ID",
- "app_secret" => "YOUR_APP_SECRET"
- }
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "facebook",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET"
+ }
+ ]
+ ```
- For installations from source:
+ For installations from source:
- ```
- - { name: 'facebook', app_id: 'YOUR_APP_ID',
- app_secret: 'YOUR_APP_SECRET' }
- ```
+ ```
+ - { name: 'facebook', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET' }
+ ```
-1. Change 'YOUR_APP_ID' to the API key from Facebook page in step 10.
+1. Change 'YOUR_APP_ID' to the API key from Facebook page in step 10.
-1. Change 'YOUR_APP_SECRET' to the API secret from the Facebook page in step 10.
+1. Change 'YOUR_APP_SECRET' to the API secret from the Facebook page in step 10.
-1. Save the configuration file.
+1. Save the configuration file.
-1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. [Reconfigure][] or [restart GitLab][] 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 a Facebook icon below the regular sign in form. Click the icon to begin the authentication process. Facebook will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/integration/github.md b/doc/integration/github.md
index 5b01dd9feb7..c8dbae65465 100644
--- a/doc/integration/github.md
+++ b/doc/integration/github.md
@@ -7,111 +7,111 @@ You can integrate your GitLab instance with GitHub.com as well as GitHub Enterpr
To enable GitHub OmniAuth provider, you must use GitHub's credentials for your GitLab instance.
To get the credentials (a pair of Client ID and Client Secret), you must register an application as an OAuth App on GitHub.
-1. Sign in to GitHub.
+1. Sign in to GitHub.
-1. Navigate to your individual user or organization settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you.
+1. Navigate to your individual user or organization settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or an organization - that is entirely up to you.
- - For individual accounts, select **Developer settings** from the left menu, then select **OAuth Apps**.
- - For organization accounts, directly select **OAuth Apps** from the left menu.
+ - For individual accounts, select **Developer settings** from the left menu, then select **OAuth Apps**.
+ - For organization accounts, directly select **OAuth Apps** from the left menu.
-1. Select **Register an application** (if you don't have any OAuth App) or **New OAuth App** (if you already have OAuth Apps).
- ![Register OAuth App](img/github_app_entry.png)
+1. Select **Register an application** (if you don't have any OAuth App) or **New OAuth App** (if you already have OAuth Apps).
+ ![Register OAuth App](img/github_app_entry.png)
-1. Provide the required details.
- - Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- - Homepage URL: The URL of your GitLab installation. For example, `https://gitlab.example.com`.
- - Application description: Fill this in if you wish.
- - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth`. Please make sure the port is included if your GitLab instance is not configured on default port.
- ![Register OAuth App](img/github_register_app.png)
+1. Provide the required details.
+ - Application name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
+ - Homepage URL: The URL of your GitLab installation. For example, `https://gitlab.example.com`.
+ - Application description: Fill this in if you wish.
+ - Authorization callback URL: `http(s)://${YOUR_DOMAIN}/users/auth`. Please make sure the port is included if your GitLab instance is not configured on default port.
+ ![Register OAuth App](img/github_register_app.png)
- NOTE: Be sure to append `/users/auth` to the end of the callback URL
- to prevent a [OAuth2 convert
- redirect](http://tetraph.com/covert_redirect/) vulnerability.
+ NOTE: Be sure to append `/users/auth` to the end of the callback URL
+ to prevent a [OAuth2 convert
+ redirect](http://tetraph.com/covert_redirect/) vulnerability.
-1. Select **Register application**.
+1. Select **Register application**.
-1. You should now see a pair of **Client ID** and **Client Secret** near the top right of the page (see screenshot).
- Keep this page open as you continue configuration.
- ![GitHub app](img/github_app.png)
+1. You should now see a pair of **Client ID** and **Client Secret** near the top right of the page (see screenshot).
+ Keep this page open as you continue configuration.
+ ![GitHub app](img/github_app.png)
-1. On your GitLab server, open the configuration file.
+1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For omnibus package:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- cd /home/git/gitlab
+ ```sh
+ cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ sudo -u git -H editor config/gitlab.yml
+ ```
-1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Add the provider configuration:
+1. Add the provider configuration:
- For omnibus package:
+ For omnibus package:
- For GitHub.com:
+ For GitHub.com:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "github",
- "app_id" => "YOUR_APP_ID",
- "app_secret" => "YOUR_APP_SECRET",
- "args" => { "scope" => "user:email" }
- }
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "github",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "args" => { "scope" => "user:email" }
+ }
+ ]
+ ```
- For GitHub Enterprise:
+ For GitHub Enterprise:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "github",
- "app_id" => "YOUR_APP_ID",
- "app_secret" => "YOUR_APP_SECRET",
- "url" => "https://github.example.com/",
- "args" => { "scope" => "user:email" }
- }
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "github",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "url" => "https://github.example.com/",
+ "args" => { "scope" => "user:email" }
+ }
+ ]
+ ```
- For installation from source:
+ For installation from source:
- For GitHub.com:
+ For GitHub.com:
- ```
- - { name: 'github', app_id: 'YOUR_APP_ID',
- app_secret: 'YOUR_APP_SECRET',
- args: { scope: 'user:email' } }
- ```
+ ```
+ - { name: 'github', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'user:email' } }
+ ```
- For GitHub Enterprise:
+ For GitHub Enterprise:
- ```
- - { name: 'github', app_id: 'YOUR_APP_ID',
- app_secret: 'YOUR_APP_SECRET',
- url: "https://github.example.com/",
- args: { scope: 'user:email' } }
- ```
+ ```
+ - { name: 'github', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ url: "https://github.example.com/",
+ args: { scope: 'user:email' } }
+ ```
- __Replace `https://github.example.com/` with your GitHub URL.__
+ __Replace `https://github.example.com/` with your GitHub URL.__
-1. Change `YOUR_APP_ID` to the Client ID from the GitHub application page from step 6.
+1. Change `YOUR_APP_ID` to the Client ID from the GitHub application page from step 6.
-1. Change `YOUR_APP_SECRET` to the Client Secret from the GitHub application page from step 6.
+1. Change `YOUR_APP_SECRET` to the Client Secret from the GitHub application page from step 6.
-1. Save the configuration file.
+1. Save the configuration file.
-1. [Reconfigure GitLab][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. [Reconfigure GitLab][] or [restart GitLab][] 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 a GitHub icon below the regular sign in form.
Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application.
@@ -127,16 +127,16 @@ and changing the global Git `sslVerify` option to `false` in the GitLab server.
For omnibus package:
```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "github",
- "app_id" => "YOUR_APP_ID",
- "app_secret" => "YOUR_APP_SECRET",
- "url" => "https://github.example.com/",
- "verify_ssl" => false,
- "args" => { "scope" => "user:email" }
- }
- ]
+gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "github",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "url" => "https://github.example.com/",
+ "verify_ssl" => false,
+ "args" => { "scope" => "user:email" }
+ }
+]
```
You will also need to disable Git SSL verification on the server hosting GitLab.
@@ -148,11 +148,11 @@ omnibus_gitconfig['system'] = { "http" => ["sslVerify = false"] }
For installation from source:
```
- - { name: 'github', app_id: 'YOUR_APP_ID',
- app_secret: 'YOUR_APP_SECRET',
- url: "https://github.example.com/",
- verify_ssl: false,
- args: { scope: 'user:email' } }
+- { name: 'github', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ url: "https://github.example.com/",
+ verify_ssl: false,
+ args: { scope: 'user:email' } }
```
You will also need to disable Git SSL verification on the server hosting GitLab.
diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md
index 70087576678..46da3d88d90 100644
--- a/doc/integration/gitlab.md
+++ b/doc/integration/gitlab.md
@@ -5,78 +5,78 @@ Import projects from GitLab.com and login to your GitLab instance with your GitL
To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com.
GitLab.com will generate an application ID and secret key for you to use.
-1. Sign in to GitLab.com
+1. Sign in to GitLab.com
1. On the upper right corner, click on your avatar and go to your **Settings**.
-1. Select **Applications** in the left menu.
+1. Select **Applications** in the left menu.
-1. Provide the required details for **Add new application**.
- - Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
- - Redirect URI:
+1. Provide the required details for **Add new application**.
+ - Name: This can be anything. Consider something like `<Organization>'s GitLab` or `<Your Name>'s GitLab` or something else descriptive.
+ - Redirect URI:
- ```
- http://your-gitlab.example.com/import/gitlab/callback
- http://your-gitlab.example.com/users/auth/gitlab/callback
- ```
+ ```
+ http://your-gitlab.example.com/import/gitlab/callback
+ http://your-gitlab.example.com/users/auth/gitlab/callback
+ ```
- The first link is required for the importer and second for the authorization.
+ The first link is required for the importer and second for the authorization.
-1. Select **Save application**.
+1. Select **Save application**.
-1. You should now see a **Application Id** and **Secret** near the top right of the page (see screenshot).
- Keep this page open as you continue configuration.
- ![GitLab app](img/gitlab_app.png)
+1. You should now see a **Application Id** and **Secret** near the top right of the page (see screenshot).
+ Keep this page open as you continue configuration.
+ ![GitLab app](img/gitlab_app.png)
-1. On your GitLab server, open the configuration file.
+1. On your GitLab server, open the configuration file.
- For omnibus package:
+ For omnibus package:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- cd /home/git/gitlab
+ ```sh
+ cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ sudo -u git -H editor config/gitlab.yml
+ ```
-1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Add the provider configuration:
+1. Add the provider configuration:
- For omnibus package:
+ For omnibus package:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "gitlab",
- "app_id" => "YOUR_APP_ID",
- "app_secret" => "YOUR_APP_SECRET",
- "args" => { "scope" => "api" }
- }
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "gitlab",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "args" => { "scope" => "api" }
+ }
+ ]
+ ```
- For installations from source:
+ For installations from source:
- ```
- - { name: 'gitlab', app_id: 'YOUR_APP_ID',
- app_secret: 'YOUR_APP_SECRET',
- args: { scope: 'api' } }
- ```
+ ```
+ - { name: 'gitlab', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { scope: 'api' } }
+ ```
-1. Change 'YOUR_APP_ID' to the Application ID from the GitLab.com application page.
+1. Change 'YOUR_APP_ID' to the Application ID from the GitLab.com application page.
-1. Change 'YOUR_APP_SECRET' to the secret from the GitLab.com application page.
+1. Change 'YOUR_APP_SECRET' to the secret from the GitLab.com application page.
-1. Save the configuration file.
+1. Save the configuration file.
-1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. [Reconfigure][] or [restart GitLab][] 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 a GitLab.com icon below the regular sign in form.
Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application.
diff --git a/doc/integration/google.md b/doc/integration/google.md
index d2b4e119978..4f6999571b6 100644
--- a/doc/integration/google.md
+++ b/doc/integration/google.md
@@ -10,10 +10,10 @@ In Google's side:
1. Navigate to the [cloud resource manager](https://console.cloud.google.com/cloud-resource-manager) page
1. Select **Create Project**
1. Provide the project information:
- - **Project name** - "GitLab" works just fine here.
- - **Project ID** - Must be unique to all Google Developer registered applications.
- Google provides a randomly generated Project ID by default. You can use
- the randomly generated ID or choose a new one.
+ - **Project name** - "GitLab" works just fine here.
+ - **Project ID** - Must be unique to all Google Developer registered applications.
+ Google provides a randomly generated Project ID by default. You can use
+ the randomly generated ID or choose a new one.
1. Refresh the page and you should see your new project in the list
1. Go to the [Google API Console](https://console.developers.google.com/apis/dashboard)
1. Select the previously created project form the upper left corner
@@ -21,17 +21,17 @@ In Google's side:
1. Select **OAuth consent screen** and fill the form with the required information
1. In the **Credentials** tab, select **Create credentials > OAuth client ID**
1. Fill in the required information
- - **Application type** - Choose "Web Application"
- - **Name** - Use the default one or provide your own
- - **Authorized JavaScript origins** -This isn't really used by GitLab but go
- ahead and put `https://gitlab.example.com`
- - **Authorized redirect URIs** - Enter your domain name followed by the
- callback URIs one at a time:
-
- ```
- https://gitlab.example.com/users/auth/google_oauth2/callback
- https://gitlab.example.com/-/google_api/auth/callback
- ```
+ - **Application type** - Choose "Web Application"
+ - **Name** - Use the default one or provide your own
+ - **Authorized JavaScript origins** -This isn't really used by GitLab but go
+ ahead and put `https://gitlab.example.com`
+ - **Authorized redirect URIs** - Enter your domain name followed by the
+ callback URIs one at a time:
+
+ ```
+ https://gitlab.example.com/users/auth/google_oauth2/callback
+ https://gitlab.example.com/-/google_api/auth/callback
+ ```
1. You should now be able to see a Client ID and Client secret. Note them down
or keep this page open as you will need them later.
@@ -45,64 +45,64 @@ On your GitLab server:
1. Open the configuration file.
- For Omnibus GitLab:
+ For Omnibus GitLab:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
1. Add the provider configuration:
- For Omnibus GitLab:
+ For Omnibus GitLab:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "google_oauth2",
- "app_id" => "YOUR_APP_ID",
- "app_secret" => "YOUR_APP_SECRET",
- "args" => { "access_type" => "offline", "approval_prompt" => '' }
- }
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "google_oauth2",
+ "app_id" => "YOUR_APP_ID",
+ "app_secret" => "YOUR_APP_SECRET",
+ "args" => { "access_type" => "offline", "approval_prompt" => '' }
+ }
+ ]
+ ```
- For installations from source:
+ For installations from source:
- ```yaml
- - { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
- app_secret: 'YOUR_APP_SECRET',
- args: { access_type: 'offline', approval_prompt: '' } }
- ```
+ ```yaml
+ - { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
+ app_secret: 'YOUR_APP_SECRET',
+ args: { access_type: 'offline', approval_prompt: '' } }
+ ```
1. Change `YOUR_APP_ID` to the client ID from the Google Developer page
1. Similarly, change `YOUR_APP_SECRET` to the client secret
1. Make sure that you configure GitLab to use an FQDN as Google will not accept
raw IP addresses.
- For Omnibus packages:
+ For Omnibus packages:
- ```ruby
- external_url 'https://gitlab.example.com'
- ```
+ ```ruby
+ external_url 'https://gitlab.example.com'
+ ```
- For installations from source:
+ For installations from source:
- ```yaml
- gitlab:
- host: https://gitlab.example.com
- ```
+ ```yaml
+ gitlab:
+ host: https://gitlab.example.com
+ ```
-1. Save the configuration file.
-1. [Reconfigure][] or [restart GitLab][] for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. Save the configuration file.
+1. [Reconfigure][] or [restart GitLab][] 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 a Google icon below the regular sign in
form. Click the icon to begin the authentication process. Google will ask the
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..5e906e5af16 100644
--- a/doc/integration/jira_development_panel.md
+++ b/doc/integration/jira_development_panel.md
@@ -59,7 +59,7 @@ There are no special requirements if you are using GitLab.com.
![GitLab Application setup](img/jira_dev_panel_gl_setup_1.png)
- Check `api` in the Scopes section.
-2. Click `Save application`. You will see the generated 'Application Id' and 'Secret' values.
+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
@@ -70,7 +70,7 @@ There are no special requirements if you are using GitLab.com.
![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.
@@ -92,7 +92,7 @@ There are no special requirements if you are using GitLab.com.
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
diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md
index cf2ef511264..b4f2025265e 100644
--- a/doc/integration/kerberos.md
+++ b/doc/integration/kerberos.md
@@ -50,20 +50,20 @@ For source installations, make sure the `kerberos` gem group
authentication. In most cases, you only need to enable Kerberos and specify
the location of the keytab:
- ```yaml
- omniauth:
- enabled: true
- allow_single_sign_on: ['kerberos']
+ ```yaml
+ omniauth:
+ enabled: true
+ allow_single_sign_on: ['kerberos']
- kerberos:
- # Allow the HTTP Negotiate authentication method for Git clients
- enabled: true
+ kerberos:
+ # Allow the HTTP Negotiate authentication method for Git clients
+ enabled: true
- # Kerberos 5 keytab file. The keytab file must be readable by the GitLab user,
- # and should be different from other keytabs in the system.
- # (default: use default keytab from Krb5 config)
- keytab: /etc/http.keytab
- ```
+ # Kerberos 5 keytab file. The keytab file must be readable by the GitLab user,
+ # and should be different from other keytabs in the system.
+ # (default: use default keytab from Krb5 config)
+ keytab: /etc/http.keytab
+ ```
1. [Restart GitLab] for the changes to take effect.
@@ -73,13 +73,13 @@ For source installations, make sure the `kerberos` gem group
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['omniauth_enabled'] = true
- gitlab_rails['omniauth_allow_single_sign_on'] = ['kerberos']
+ ```ruby
+ gitlab_rails['omniauth_enabled'] = true
+ gitlab_rails['omniauth_allow_single_sign_on'] = ['kerberos']
- gitlab_rails['kerberos_enabled'] = true
- gitlab_rails['kerberos_keytab'] = "/etc/http.keytab"
- ```
+ gitlab_rails['kerberos_enabled'] = true
+ gitlab_rails['kerberos_keytab'] = "/etc/http.keytab"
+ ```
1. [Reconfigure GitLab] for the changes to take effect.
@@ -149,26 +149,26 @@ keep offering only `basic` authentication.
(e.g., `/etc/nginx/sites-available/gitlab-ssl`) and configure NGINX to
listen to port `8443` in addition to the standard HTTPS port:
- ```conf
- server {
- listen 0.0.0.0:443 ssl;
- listen [::]:443 ipv6only=on ssl default_server;
- listen 0.0.0.0:8443 ssl;
- listen [::]:8443 ipv6only=on ssl;
- ```
+ ```conf
+ server {
+ listen 0.0.0.0:443 ssl;
+ listen [::]:443 ipv6only=on ssl default_server;
+ listen 0.0.0.0:8443 ssl;
+ listen [::]:8443 ipv6only=on ssl;
+ ```
1. Update the Kerberos section of [gitlab.yml]:
- ```yaml
- kerberos:
- # Dedicated port: Git before 2.4 does not fall back to Basic authentication if Negotiate fails.
- # To support both Basic and Negotiate methods with older versions of Git, configure
- # nginx to proxy GitLab on an extra port (e.g. 8443) and uncomment the following lines
- # to dedicate this port to Kerberos authentication. (default: false)
- use_dedicated_port: true
- port: 8443
- https: true
- ```
+ ```yaml
+ kerberos:
+ # Dedicated port: Git before 2.4 does not fall back to Basic authentication if Negotiate fails.
+ # To support both Basic and Negotiate methods with older versions of Git, configure
+ # nginx to proxy GitLab on an extra port (e.g. 8443) and uncomment the following lines
+ # to dedicate this port to Kerberos authentication. (default: false)
+ use_dedicated_port: true
+ port: 8443
+ https: true
+ ```
1. [Restart GitLab] and NGINX for the changes to take effect.
@@ -178,11 +178,11 @@ keep offering only `basic` authentication.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['kerberos_use_dedicated_port'] = true
- gitlab_rails['kerberos_port'] = 8443
- gitlab_rails['kerberos_https'] = true
- ```
+ ```ruby
+ gitlab_rails['kerberos_use_dedicated_port'] = true
+ gitlab_rails['kerberos_port'] = 8443
+ gitlab_rails['kerberos_https'] = true
+ ```
1. [Reconfigure GitLab] for the changes to take effect.
@@ -214,12 +214,12 @@ remove the OmniAuth provider named `kerberos` from your `gitlab.yml` /
1. Edit [gitlab.yml] and remove the `- { name: 'kerberos' }` line under omniauth
providers:
- ```yaml
- omniauth:
- # ...
- providers:
- - { name: 'kerberos' } # <-- remove this line
- ```
+ ```yaml
+ omniauth:
+ # ...
+ providers:
+ - { name: 'kerberos' } # <-- remove this line
+ ```
1. [Restart GitLab] for the changes to take effect.
@@ -230,11 +230,11 @@ remove the OmniAuth provider named `kerberos` from your `gitlab.yml` /
1. Edit `/etc/gitlab/gitlab.rb` and remove the `{ "name" => "kerberos" }` line
under `gitlab_rails['omniauth_providers']`:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- { "name" => "kerberos" } # <-- remove this entry
- ]
- ```
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ { "name" => "kerberos" } # <-- remove this entry
+ ]
+ ```
1. [Reconfigure GitLab] for the changes to take effect.
@@ -290,7 +290,7 @@ remote: HTTP Basic: Access denied
fatal: Authentication failed for '<KRB5 path>'
```
-If you are using Git v2.11 or newer and see the above error when cloning, you can
+If you are using Git v2.11 or newer and see the above error when cloning, you can
set the `http.emptyAuth` Git option to `true` to fix this:
```
diff --git a/doc/integration/oauth2_generic.md b/doc/integration/oauth2_generic.md
index 3c1a0f2a117..f4119b1d1ce 100644
--- a/doc/integration/oauth2_generic.md
+++ b/doc/integration/oauth2_generic.md
@@ -24,11 +24,11 @@ This strategy is designed to allow configuration of the simple OmniAuth SSO proc
1. Register your application in the OAuth2 provider you wish to authenticate with.
- The redirect URI you provide when registering the application should be:
+ The redirect URI you provide when registering the application should be:
- ```
- http://your-gitlab.host.com/users/auth/oauth2_generic/callback
- ```
+ ```
+ http://your-gitlab.host.com/users/auth/oauth2_generic/callback
+ ```
1. You should now be able to get a Client ID and Client Secret.
Where this shows up will differ for each provider.
@@ -36,18 +36,18 @@ This strategy is designed to allow configuration of the simple OmniAuth SSO proc
1. On your GitLab server, open the configuration file.
- For Omnibus package:
+ For Omnibus package:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index b9dc2e123c5..36b4836e6b3 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -25,8 +25,6 @@ can be used for authentication to your GitLab instance
The 'GitLab Importer' feature is also using the OAuth protocol to give access
to repositories without sharing user credentials to your GitLab.com account.
----
-
GitLab supports two ways of adding a new OAuth2 application to an instance. You
can either add an application as a regular user or add it in the admin area.
What this means is that GitLab can actually have instance-wide and a user-wide
@@ -41,24 +39,18 @@ In order to add a new application via your profile, navigate to
![New OAuth application](img/oauth_provider_user_wide_applications.png)
----
-
In the application form, enter a **Name** (arbitrary), and make sure to set up
correctly the **Redirect URI** which is the URL where users will be sent after
they authorize with GitLab.
![New OAuth application form](img/oauth_provider_application_form.png)
----
-
When you hit **Submit** you will be provided with the application ID and
the application secret which you can then use with your application that
connects to GitLab.
![OAuth application ID and secret](img/oauth_provider_application_id_secret.png)
----
-
## OAuth applications in the admin area
To create an application that does not belong to a certain user, you can create
@@ -69,8 +61,6 @@ it from the admin area.
You're also able to mark an application as _trusted_ when creating it through the admin area. By doing that,
the user authorization step is automatically skipped for this application.
----
-
## Authorized applications
Every application you authorized to use your GitLab credentials will be shown
@@ -78,8 +68,6 @@ in the **Authorized applications** section under **Profile Settings > Applicatio
![Authorized_applications](img/oauth_provider_authorized_application.png)
----
-
GitLab's OAuth applications support scopes, which allow various actions that any given
application can perform such as `read_user` and `api`. There are many more scopes
available.
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index bf5debc7694..7a92ed994c7 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -71,57 +71,57 @@ To change these settings:
- **For omnibus package**
- Open the configuration file:
+ Open the configuration file:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- and change:
+ and change:
- ```ruby
- # Versions prior to 11.4 require this to be set to true
- # gitlab_rails['omniauth_enabled'] = nil
+ ```ruby
+ # Versions prior to 11.4 require this to be set to true
+ # gitlab_rails['omniauth_enabled'] = nil
- # CAUTION!
- # This allows users to login without having a user account first. Define the allowed providers
- # using an array, e.g. ["saml", "twitter"], or as true/false to allow all providers or none.
- # User accounts will be created automatically when authentication was successful.
- gitlab_rails['omniauth_allow_single_sign_on'] = ['saml', 'twitter']
- gitlab_rails['omniauth_auto_link_ldap_user'] = true
- gitlab_rails['omniauth_block_auto_created_users'] = true
- ```
+ # CAUTION!
+ # This allows users to login without having a user account first. Define the allowed providers
+ # using an array, e.g. ["saml", "twitter"], or as true/false to allow all providers or none.
+ # User accounts will be created automatically when authentication was successful.
+ gitlab_rails['omniauth_allow_single_sign_on'] = ['saml', 'twitter']
+ gitlab_rails['omniauth_auto_link_ldap_user'] = true
+ gitlab_rails['omniauth_block_auto_created_users'] = true
+ ```
- **For installations from source**
- Open the configuration file:
+ Open the configuration file:
- ```sh
- cd /home/git/gitlab
+ ```sh
+ cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+ sudo -u git -H editor config/gitlab.yml
+ ```
- and change the following section:
+ and change the following section:
- ```yaml
- ## OmniAuth settings
- omniauth:
- # Allow login via Twitter, Google, etc. using OmniAuth providers
- # Versions prior to 11.4 require this to be set to true
- # enabled: true
+ ```yaml
+ ## OmniAuth settings
+ omniauth:
+ # Allow login via Twitter, Google, etc. using OmniAuth providers
+ # Versions prior to 11.4 require this to be set to true
+ # enabled: true
- # CAUTION!
- # This allows users to login without having a user account first. Define the allowed providers
- # using an array, e.g. ["saml", "twitter"], or as true/false to allow all providers or none.
- # User accounts will be created automatically when authentication was successful.
- allow_single_sign_on: ["saml", "twitter"]
+ # CAUTION!
+ # This allows users to login without having a user account first. Define the allowed providers
+ # using an array, e.g. ["saml", "twitter"], or as true/false to allow all providers or none.
+ # User accounts will be created automatically when authentication was successful.
+ allow_single_sign_on: ["saml", "twitter"]
- auto_link_ldap_user: true
+ auto_link_ldap_user: true
- # Locks down those users until they have been cleared by the admin (default: true).
- block_auto_created_users: true
- ```
+ # Locks down those users until they have been cleared by the admin (default: true).
+ block_auto_created_users: true
+ ```
Now we can choose one or more of the [Supported Providers](#supported-providers)
listed above to continue the configuration process.
@@ -161,14 +161,14 @@ want their accounts to be upgraded to full internal accounts.
**For Omnibus installations**
```ruby
- gitlab_rails['omniauth_external_providers'] = ['twitter', 'google_oauth2']
+gitlab_rails['omniauth_external_providers'] = ['twitter', 'google_oauth2']
```
**For installations from source**
```yaml
- omniauth:
- external_providers: ['twitter', 'google_oauth2']
+omniauth:
+ external_providers: ['twitter', 'google_oauth2']
```
## Using Custom Omniauth Providers
@@ -186,23 +186,31 @@ these cases you can use the Omniauth provider.
These steps are fairly general and you will need to figure out the exact details
from the Omniauth provider's documentation.
-- Stop GitLab:
+- Stop GitLab:
- sudo service gitlab stop
+ ```sh
+ sudo service gitlab stop
+ ```
-- Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile):
+- Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile):
- gem "omniauth-your-auth-provider"
+ ```sh
+ gem "omniauth-your-auth-provider"
+ ```
-- Install the new Omniauth provider gem by running the following command:
+- Install the new Omniauth provider gem by running the following command:
- sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
+ ```sh
+ sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment
+ ```
- > These are the same commands you used during initial installation in the [Install Gems section](../install/installation.md#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
+ > These are the same commands you used during initial installation in the [Install Gems section](../install/installation.md#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`.
-- Start GitLab:
+- Start GitLab:
- sudo service gitlab start
+ ```sh
+ sudo service gitlab start
+ ```
### Examples
@@ -247,8 +255,8 @@ gitlab_rails['omniauth_enabled'] = false
**For installations from source**
```yaml
- omniauth:
- enabled: false
+omniauth:
+ enabled: false
```
## Keep OmniAuth user profiles up to date
@@ -258,14 +266,14 @@ You can enable profile syncing from selected OmniAuth providers and for all or f
When authenticating using LDAP, the user's name and email are always synced.
```ruby
- gitlab_rails['sync_profile_from_provider'] = ['twitter', 'google_oauth2']
- gitlab_rails['sync_profile_attributes'] = ['name', 'email', 'location']
- ```
+gitlab_rails['sync_profile_from_provider'] = ['twitter', 'google_oauth2']
+gitlab_rails['sync_profile_attributes'] = ['name', 'email', 'location']
+```
**For installations from source**
```yaml
- omniauth:
- sync_profile_from_provider: ['twitter', 'google_oauth2']
- sync_profile_attributes: ['email', 'location']
+omniauth:
+ sync_profile_from_provider: ['twitter', 'google_oauth2']
+ sync_profile_attributes: ['email', 'location']
```
diff --git a/doc/integration/salesforce.md b/doc/integration/salesforce.md
index 1ef43cfcece..176622e8050 100644
--- a/doc/integration/salesforce.md
+++ b/doc/integration/salesforce.md
@@ -7,73 +7,77 @@ You can integrate your GitLab instance with [Salesforce](https://www.salesforce.
To enable Salesforce OmniAuth provider, you must use Salesforce's credentials for your GitLab instance.
To get the credentials (a pair of Client ID and Client Secret), you must [create a Connected App](https://help.salesforce.com/articleView?id=connected_app_create.htm&type=5) on Salesforce.
-1. Sign in to [Salesforce](https://login.salesforce.com/).
+1. Sign in to [Salesforce](https://login.salesforce.com/).
-1. In Setup, enter `App Manager` in the Quick Find box, click **App Manager**, then click **New Connected App**.
+1. In Setup, enter `App Manager` in the Quick Find box, click **App Manager**, then click **New Connected App**.
-1. Fill in the application details into the following fields:
- - **Connected App Name** and **API Name**: Set to any value but consider something like `<Organization>'s GitLab`, `<Your Name>'s GitLab`, or something else that is descriptive.
- - **Contact Email**: Enter the contact email for Salesforce to use when contacting you or your support team.
- - **Description**: Description for the application.
+1. Fill in the application details into the following fields:
+ - **Connected App Name** and **API Name**: Set to any value but consider something like `<Organization>'s GitLab`, `<Your Name>'s GitLab`, or something else that is descriptive.
+ - **Contact Email**: Enter the contact email for Salesforce to use when contacting you or your support team.
+ - **Description**: Description for the application.
- ![Salesforce App Details](img/salesforce_app_details.png)
-1. Select **API (Enable OAuth Settings)** and click on **Enable OAuth Settings**.
-1. Fill in the application details into the following fields:
- - **Callback URL**: The callback URL of your GitLab installation. For example, `https://gitlab.example.com/users/auth/salesforce/callback`.
- - **Selected OAuth Scopes**: Move **Access your basic information (id, profile, email, address, phone)** and **Allow access to your unique identifier (openid)** to the right column.
+ ![Salesforce App Details](img/salesforce_app_details.png)
+
+1. Select **API (Enable OAuth Settings)** and click on **Enable OAuth Settings**.
+1. Fill in the application details into the following fields:
+ - **Callback URL**: The callback URL of your GitLab installation. For example, `https://gitlab.example.com/users/auth/salesforce/callback`.
+ - **Selected OAuth Scopes**: Move **Access your basic information (id, profile, email, address, phone)** and **Allow access to your unique identifier (openid)** to the right column.
+
+ ![Salesforce Oauth App Details](img/salesforce_oauth_app_details.png)
- ![Salesforce Oauth App Details](img/salesforce_oauth_app_details.png)
1. Click **Save**.
-1. On your GitLab server, open the configuration file.
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
- For omnibus package:
+ For installations from source:
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
- For installations from source:
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
- ```sh
- cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
+1. Add the provider configuration:
-1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+ For omnibus package:
-1. Add the provider configuration:
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "salesforce",
+ "app_id" => "SALESFORCE_CLIENT_ID",
+ "app_secret" => "SALESFORCE_CLIENT_SECRET"
+ }
+ ]
+ ```
- For omnibus package:
+ For installation from source:
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "salesforce",
- "app_id" => "SALESFORCE_CLIENT_ID",
- "app_secret" => "SALESFORCE_CLIENT_SECRET"
- }
- ]
- ```
+ ```
+ - { name: 'salesforce',
+ app_id: 'SALESFORCE_CLIENT_ID',
+ app_secret: 'SALESFORCE_CLIENT_SECRET'
+ }
+ ```
- For installation from source:
+1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the Salesforce connected application page.
+1. Change `SALESFORCE_CLIENT_SECRET` to the Consumer Secret from the Salesforce connected application page.
- ```
- - { name: 'salesforce',
- app_id: 'SALESFORCE_CLIENT_ID',
- app_secret: 'SALESFORCE_CLIENT_SECRET'
- }
- ```
-1. Change `SALESFORCE_CLIENT_ID` to the Consumer Key from the Salesforce connected application page.
-1. Change `SALESFORCE_CLIENT_SECRET` to the Consumer Secret from the Salesforce connected application page.
- ![Salesforce App Secret Details](img/salesforce_app_secret_details.png)
+ ![Salesforce App Secret Details](img/salesforce_app_secret_details.png)
-1. Save the configuration file.
-1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/restart_gitlab.md#installations-from-source ) for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
+1. Save the configuration file.
+1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/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 a Salesforce icon below the regular sign in form.
Click the icon to begin the authentication process. Salesforce will ask the user to sign in and authorize the GitLab application.
If everything goes well, the user will be returned to GitLab and will be signed in.
NOTE: **Note:**
-GitLab requires the email address of each new user. Once the user is logged in using Salesforce, GitLab will redirect the user to the profile page where they will have to provide the email and verify the email. \ No newline at end of file
+GitLab requires the email address of each new user. Once the user is logged in using Salesforce, GitLab will redirect the user to the profile page where they will have to provide the email and verify the email.
diff --git a/doc/integration/shibboleth.md b/doc/integration/shibboleth.md
index 07c83c1a049..27355d25266 100644
--- a/doc/integration/shibboleth.md
+++ b/doc/integration/shibboleth.md
@@ -14,35 +14,35 @@ The following changes are needed to enable Shibboleth:
1. Protect omniauth-shibboleth callback URL:
- ```
- <Location /users/auth/shibboleth/callback>
- AuthType shibboleth
- ShibRequestSetting requireSession 1
- ShibUseHeaders On
- require valid-user
- </Location>
-
- Alias /shibboleth-sp /usr/share/shibboleth
- <Location /shibboleth-sp>
- Satisfy any
- </Location>
-
- <Location /Shibboleth.sso>
- SetHandler shib
- </Location>
- ```
+ ```
+ <Location /users/auth/shibboleth/callback>
+ AuthType shibboleth
+ ShibRequestSetting requireSession 1
+ ShibUseHeaders On
+ require valid-user
+ </Location>
+
+ Alias /shibboleth-sp /usr/share/shibboleth
+ <Location /shibboleth-sp>
+ Satisfy any
+ </Location>
+
+ <Location /Shibboleth.sso>
+ SetHandler shib
+ </Location>
+ ```
1. Exclude shibboleth URLs from rewriting. Add `RewriteCond %{REQUEST_URI} !/Shibboleth.sso` and `RewriteCond %{REQUEST_URI} !/shibboleth-sp`. Config should look like this:
- ```
- # Apache equivalent of Nginx try files
- RewriteEngine on
- RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
- RewriteCond %{REQUEST_URI} !/Shibboleth.sso
- RewriteCond %{REQUEST_URI} !/shibboleth-sp
- RewriteRule .* http://127.0.0.1:8080%{REQUEST_URI} [P,QSA]
- RequestHeader set X_FORWARDED_PROTO 'https'
- ```
+ ```
+ # Apache equivalent of Nginx try files
+ RewriteEngine on
+ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_URI} !/Shibboleth.sso
+ RewriteCond %{REQUEST_URI} !/shibboleth-sp
+ RewriteRule .* http://127.0.0.1:8080%{REQUEST_URI} [P,QSA]
+ RequestHeader set X_FORWARDED_PROTO 'https'
+ ```
1. Edit `/etc/gitlab/gitlab.rb` configuration file to enable OmniAuth and add
Shibboleth as an OmniAuth provider. User attributes will be sent from the
@@ -60,31 +60,31 @@ The following changes are needed to enable Shibboleth:
The file should look like this:
- ```
- external_url 'https://gitlab.example.com'
- gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
-
- # disable Nginx
- nginx['enable'] = false
-
- gitlab_rails['omniauth_allow_single_sign_on'] = true
- gitlab_rails['omniauth_block_auto_created_users'] = false
- gitlab_rails['omniauth_enabled'] = true
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "'shibboleth"',
- "label" => "Text for Login Button",
- "args" => {
- "shib_session_id_field" => "HTTP_SHIB_SESSION_ID",
- "shib_application_id_field" => "HTTP_SHIB_APPLICATION_ID",
- "uid_field" => 'HTTP_EPPN',
- "name_field" => 'HTTP_CN',
- "info_fields" => { "email" => 'HTTP_MAIL'}
- }
- }
- ]
-
- ```
+ ```
+ external_url 'https://gitlab.example.com'
+ gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
+
+ # disable Nginx
+ nginx['enable'] = false
+
+ gitlab_rails['omniauth_allow_single_sign_on'] = true
+ gitlab_rails['omniauth_block_auto_created_users'] = false
+ gitlab_rails['omniauth_enabled'] = true
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "'shibboleth"',
+ "label" => "Text for Login Button",
+ "args" => {
+ "shib_session_id_field" => "HTTP_SHIB_SESSION_ID",
+ "shib_application_id_field" => "HTTP_SHIB_APPLICATION_ID",
+ "uid_field" => 'HTTP_EPPN',
+ "name_field" => 'HTTP_CN',
+ "info_fields" => { "email" => 'HTTP_MAIL'}
+ }
+ }
+ ]
+
+ ```
1. [Reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart](../administration/restart_gitlab.md#installations-from-source) GitLab for the changes to take effect if you
installed GitLab via Omnibus or from source respectively.
@@ -97,44 +97,44 @@ The order of the first 2 Location directives is important. If they are reversed,
you will not get a shibboleth session!
```
- <Location />
- Require all granted
- ProxyPassReverse http://127.0.0.1:8181
- ProxyPassReverse http://YOUR_SERVER_FQDN/
- </Location>
-
- <Location /users/auth/shibboleth/callback>
- AuthType shibboleth
- ShibRequestSetting requireSession 1
- ShibUseHeaders On
- Require shib-session
- </Location>
-
- Alias /shibboleth-sp /usr/share/shibboleth
-
- <Location /shibboleth-sp>
- Require all granted
- </Location>
-
- <Location /Shibboleth.sso>
- SetHandler shib
- </Location>
-
- RewriteEngine on
-
- #Don't escape encoded characters in api requests
- RewriteCond %{REQUEST_URI} ^/api/v4/.*
- RewriteCond %{REQUEST_URI} !/Shibboleth.sso
- RewriteCond %{REQUEST_URI} !/shibboleth-sp
- RewriteRule .* http://127.0.0.1:8181%{REQUEST_URI} [P,QSA,NE]
-
- #Forward all requests to gitlab-workhorse except existing files
- RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f [OR]
- RewriteCond %{REQUEST_URI} ^/uploads/.*
- RewriteCond %{REQUEST_URI} !/Shibboleth.sso
- RewriteCond %{REQUEST_URI} !/shibboleth-sp
- RewriteRule .* http://127.0.0.1:8181%{REQUEST_URI} [P,QSA]
-
- RequestHeader set X_FORWARDED_PROTO 'https'
- RequestHeader set X-Forwarded-Ssl on
+<Location />
+ Require all granted
+ ProxyPassReverse http://127.0.0.1:8181
+ ProxyPassReverse http://YOUR_SERVER_FQDN/
+</Location>
+
+<Location /users/auth/shibboleth/callback>
+ AuthType shibboleth
+ ShibRequestSetting requireSession 1
+ ShibUseHeaders On
+ Require shib-session
+</Location>
+
+Alias /shibboleth-sp /usr/share/shibboleth
+
+<Location /shibboleth-sp>
+ Require all granted
+</Location>
+
+<Location /Shibboleth.sso>
+ SetHandler shib
+</Location>
+
+RewriteEngine on
+
+#Don't escape encoded characters in api requests
+RewriteCond %{REQUEST_URI} ^/api/v4/.*
+RewriteCond %{REQUEST_URI} !/Shibboleth.sso
+RewriteCond %{REQUEST_URI} !/shibboleth-sp
+RewriteRule .* http://127.0.0.1:8181%{REQUEST_URI} [P,QSA,NE]
+
+#Forward all requests to gitlab-workhorse except existing files
+RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f [OR]
+RewriteCond %{REQUEST_URI} ^/uploads/.*
+RewriteCond %{REQUEST_URI} !/Shibboleth.sso
+RewriteCond %{REQUEST_URI} !/shibboleth-sp
+RewriteRule .* http://127.0.0.1:8181%{REQUEST_URI} [P,QSA]
+
+RequestHeader set X_FORWARDED_PROTO 'https'
+RequestHeader set X-Forwarded-Ssl on
```
diff --git a/doc/integration/ultra_auth.md b/doc/integration/ultra_auth.md
index 69b2a75050d..9ed1bdb4882 100644
--- a/doc/integration/ultra_auth.md
+++ b/doc/integration/ultra_auth.md
@@ -7,69 +7,78 @@ You can integrate your GitLab instance with [UltraAuth](https://ultraauth.com) t
To enable UltraAuth OmniAuth provider, you must use UltraAuth's credentials for your GitLab instance.
To get the credentials (a pair of Client ID and Client Secret), you must register an application on UltraAuth.
-1. Sign in to [UltraAuth](https://ultraauth.com).
-1. Navigate to [Create an App](https://ultraauth.com/select-strategy) and click on "Ruby on Rails".
-1. Scroll down the page that is displayed to locate the **Client ID** and **Client Secret**.
- Keep this page open as you continue configuration.
- ![UltraAuth Credentials: OPENID_CLIENT_ID and OPENID_CLIENT_SECRET](img/ultra_auth_credentials.png)
-1. Click on "Edit Callback URL" link.
- ![Edit UltraAuth Callback URL](img/ultra_auth_edit_callback_url_highlighted.png)
-1. The callback URL will be `http(s)://<your_domain>/users/auth/ultraauth/callback`
- ![UltraAuth Callback URL](img/ultra_auth_edit_callback_url.png)
-1. Select **Register application**.
-1. On your GitLab server, open the configuration file.
-
- For omnibus package:
-
- ```sh
- sudo editor /etc/gitlab/gitlab.rb
- ```
-
- For installations from source:
-
- ```sh
- cd /home/git/gitlab
- sudo -u git -H editor config/gitlab.yml
- ```
-1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
-1. Add the provider configuration:
-
- For omnibus package:
-
- ```ruby
- gitlab_rails['omniauth_providers'] = [
- {
- "name" => "ultraauth",
- "app_id" => "OPENID_CLIENT_ID",
- "app_secret" => "OPENID_CLIENT_SECRET",
- "args" => {
- "client_options" => {
- "redirect_uri" => "https://example.com/users/auth/ultraauth/callback"
- }
- }
- }
- ]
- ```
-
- For installation from source:
-
- ```
- - { name: 'ultraauth',
- app_id: 'OPENID_CLIENT_ID',
- app_secret: 'OPENID_CLIENT_SECRET',
- args: {
- client_options: {
- redirect_uri: 'https://example.com/users/auth/ultraauth/callback'
- }
- }
- }
- ```
- __Replace `https://example.com/users/auth/ultraauth/callback` with your application's Callback URL.__
-1. Change `OPENID_CLIENT_ID` to the Client ID from the UltraAuth application page.
-1. Change `OPENID_CLIENT_SECRET` to the Client Secret from the UltraAuth application page.
-1. Save the configuration file.
-1. [Reconfigure GitLab]( ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure ) or [restart GitLab]( ../administration/restart_gitlab.md#installations-from-source ) for the changes to take effect if you
- installed GitLab via Omnibus or from source respectively.
+1. Sign in to [UltraAuth](https://ultraauth.com).
+1. Navigate to [Create an App](https://ultraauth.com/select-strategy) and click on "Ruby on Rails".
+1. Scroll down the page that is displayed to locate the **Client ID** and **Client Secret**.
+ Keep this page open as you continue configuration.
+
+ ![UltraAuth Credentials: OPENID_CLIENT_ID and OPENID_CLIENT_SECRET](img/ultra_auth_credentials.png)
+
+1. Click on "Edit Callback URL" link.
+
+ ![Edit UltraAuth Callback URL](img/ultra_auth_edit_callback_url_highlighted.png)
+
+1. The callback URL will be `http(s)://<your_domain>/users/auth/ultraauth/callback`
+
+ ![UltraAuth Callback URL](img/ultra_auth_edit_callback_url.png)
+
+1. Select **Register application**.
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations from source:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "ultraauth",
+ "app_id" => "OPENID_CLIENT_ID",
+ "app_secret" => "OPENID_CLIENT_SECRET",
+ "args" => {
+ "client_options" => {
+ "redirect_uri" => "https://example.com/users/auth/ultraauth/callback"
+ }
+ }
+ }
+ ]
+ ```
+
+ For installation from source:
+
+ ```
+ - { name: 'ultraauth',
+ app_id: 'OPENID_CLIENT_ID',
+ app_secret: 'OPENID_CLIENT_SECRET',
+ args: {
+ client_options: {
+ redirect_uri: 'https://example.com/users/auth/ultraauth/callback'
+ }
+ }
+ }
+ ```
+
+ __Replace `https://example.com/users/auth/ultraauth/callback` with your application's Callback URL.__
+
+1. Change `OPENID_CLIENT_ID` to the Client ID from the UltraAuth application page.
+1. Change `OPENID_CLIENT_SECRET` to the Client Secret from the UltraAuth application page.
+1. Save the configuration file.
+1. [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../administration/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 UltraAuth icon below the regular sign in form.
Click the icon to begin the authentication process. UltraAuth will ask the user to sign in and authorize the GitLab application.
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 7659b743311..51ff56b3541 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -181,7 +181,7 @@ sudo -u gitlab_ci -H bundle exec rake backup:show_secrets RAILS_ENV=production
### 2. SQL data and build traces
Create your final CI data export. If you are converting from MySQL to
- PostgreSQL, add ` MYSQL_TO_POSTGRESQL=1` to the end of the rake command. When
+PostgreSQL, add `MYSQL_TO_POSTGRESQL=1` to the end of the rake command. When
the command finishes it will print the path to your data export archive; you
will need this file later.
@@ -323,11 +323,15 @@ You should also make sure that you can:
### 2. Check Nginx configuration
- sudo nginx -t
+```sh
+sudo nginx -t
+```
### 3. Restart Nginx
- sudo /etc/init.d/nginx restart
+```sh
+sudo /etc/init.d/nginx restart
+```
### Restore from backup
@@ -352,11 +356,13 @@ The fix for this is to update to Omnibus 7.14 first and then update it to 8.0.
### Permission denied when accessing /var/opt/gitlab/gitlab-ci/builds
To fix that issue you have to change builds/ folder permission before doing final backup:
+
```
sudo chown -R gitlab-ci:gitlab-ci /var/opt/gitlab/gitlab-ci/builds
```
Then before executing `ci:migrate` you need to fix builds folder permission:
+
```
sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
```
@@ -365,7 +371,7 @@ sudo chown git:git /var/opt/gitlab/gitlab-ci/builds
If you were migrating CI database from MySQL to PostgreSQL manually you can see errors during import about missing sequences:
-```sql
+```sql
ALTER SEQUENCE
ERROR: relation "ci_builds_id_seq" does not exist
ERROR: relation "ci_commits_id_seq" does not exist
diff --git a/doc/pages/getting_started_part_three.md b/doc/pages/getting_started_part_three.md
index b65247ff7b7..31a01a6c83b 100644
--- a/doc/pages/getting_started_part_three.md
+++ b/doc/pages/getting_started_part_three.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../user/project/pages/getting_started_part_three.md'
+redirect_to: '../user/project/pages/custom_domains_ssl_tls_certification/index.md'
---
-This document was moved to [another location](../user/project/pages/getting_started_part_three.md).
+This document was moved to [another location](../user/project/pages/custom_domains_ssl_tls_certification/index.md).
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 092b4375208..0d86df04367 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -315,8 +315,6 @@ if you see `411 Length Required` errors after attempting to upload, you may
need to downgrade the `aws_signature_version` value from the default value to
2 [due to this issue](https://github.com/fog/fog-aws/issues/428).
----
-
For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
@@ -428,8 +426,6 @@ For Omnibus GitLab packages:
1. [Reconfigure GitLab] for the changes to take effect
----
-
For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
@@ -490,8 +486,6 @@ For Omnibus GitLab packages:
1. [Reconfigure GitLab] for the changes to take effect.
----
-
For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
@@ -527,8 +521,6 @@ For Omnibus GitLab packages:
1. [Reconfigure GitLab] for the changes to take effect.
----
-
For installations from source:
1. Edit `/home/git/gitlab/config/gitlab.yml`:
@@ -580,8 +572,6 @@ There, add the following line to schedule the backup for everyday at 2 AM:
You may also want to set a limited lifetime for backups to prevent regular
backups using all your disk space.
----
-
For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
@@ -741,9 +731,10 @@ sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2018_04_25_10.6.4-ce
Next, restore `/etc/gitlab/gitlab-secrets.json` if necessary as mentioned above.
-Restart and check GitLab:
+Reconfigure, restart and check GitLab:
```shell
+sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart
sudo gitlab-rake gitlab:check SANITIZE=true
```
@@ -814,6 +805,7 @@ will have all your repositories, but not any other data.
## Troubleshooting
### Restoring database backup using omnibus packages outputs warnings
+
If you are using backup restore procedures you might encounter the following warnings:
```
@@ -842,7 +834,7 @@ including (but not restricted to):
- [CI/CD variables](../ci/variables/README.md)
- [Kubernetes / GCP integration](../user/project/clusters/index.md)
-- [Custom Pages domains](../user/project/pages/getting_started_part_three.md)
+- [Custom Pages domains](../user/project/pages/custom_domains_ssl_tls_certification/index.md)
- [Project error tracking](../user/project/operations/error_tracking.md)
- [Runner authentication](../ci/runners/README.md)
- [Project mirroring](../workflow/repository_mirroring.md)
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/import.md b/doc/raketasks/import.md
index b59c06a24ea..8f65fab366e 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -100,7 +100,7 @@ the git repository's config file. This section is formatted as follows:
```
[gitlab]
- fullpath = gitlab-org/gitlab-ce
+ fullpath = gitlab-org/gitlab-ce
```
However, existing repositories were not migrated to include this path.
diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md
index 62a20d3f257..749ccf924b5 100644
--- a/doc/security/information_exclusivity.md
+++ b/doc/security/information_exclusivity.md
@@ -1,6 +1,7 @@
---
type: concepts
---
+
# Information exclusivity
Git is a distributed version control system (DVCS). This means that everyone
diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md
index d78293c75c6..9909ef4a8e4 100644
--- a/doc/security/password_length_limits.md
+++ b/doc/security/password_length_limits.md
@@ -1,19 +1,31 @@
---
type: reference, howto
---
+
# Custom password length limits
-If you want to enforce longer user passwords you can create an extra Devise
-initializer with the steps below.
+The user password length is set to a minimum of 8 characters by default.
+To change that for installations from source:
+
+1. Edit `devise_password_length.rb`:
+
+ ```sh
+ cd /home/git/gitlab
+ sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb
+ sudo -u git -H editor config/initializers/devise_password_length.rb
+ ```
+
+1. Change the new password length limits:
+
+ ```ruby
+ config.password_length = 12..128
+ ```
-If you do not use the `devise_password_length.rb` initializer the password
-length is set to a minimum of 8 characters in `config/initializers/devise.rb`.
+ In this example, the minimum length is 12 characters, and the maximum length
+ is 128 characters.
-```bash
-cd /home/git/gitlab
-sudo -u git -H cp config/initializers/devise_password_length.rb.example config/initializers/devise_password_length.rb
-sudo -u git -H editor config/initializers/devise_password_length.rb # inspect and edit the new password length limits
-```
+1. [Restart GitLab](../administration/restart_gitlab.md#installations-from-source)
+ for the changes to take effect.
<!-- ## Troubleshooting
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index 8695b5d2194..1e5678ec47c 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -1,6 +1,7 @@
---
type: reference, howto
---
+
# Rack Attack
[Rack Attack](https://github.com/kickstarter/rack-attack), also known as Rack::Attack, is a Ruby gem
@@ -34,34 +35,34 @@ For more information on how to use these options check out
1. Open `/etc/gitlab/gitlab.rb` with your editor
1. Add the following:
- ```ruby
- gitlab_rails['rack_attack_git_basic_auth'] = {
- 'enabled' => true,
- 'ip_whitelist' => ["127.0.0.1"],
- 'maxretry' => 10, # Limit the number of Git HTTP authentication attempts per IP
- 'findtime' => 60, # Reset the auth attempt counter per IP after 60 seconds
- 'bantime' => 3600 # Ban an IP for one hour (3600s) after too many auth attempts
- }
- ```
+ ```ruby
+ gitlab_rails['rack_attack_git_basic_auth'] = {
+ 'enabled' => true,
+ 'ip_whitelist' => ["127.0.0.1"],
+ 'maxretry' => 10, # Limit the number of Git HTTP authentication attempts per IP
+ 'findtime' => 60, # Reset the auth attempt counter per IP after 60 seconds
+ 'bantime' => 3600 # Ban an IP for one hour (3600s) after too many auth attempts
+ }
+ ```
1. Reconfigure GitLab:
- ```
- sudo gitlab-ctl reconfigure
- ```
+ ```
+ sudo gitlab-ctl reconfigure
+ ```
The following settings can be configured:
- `enabled`: By default this is set to `false`. Set this to `true` to enable Rack Attack.
- `ip_whitelist`: Whitelist any IPs from being blocked. They must be formatted as strings within a Ruby array.
- CIDR notation is supported in GitLab v12.1 and up.
- For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3", "192.168.0.1/24"]`.
+ CIDR notation is supported in GitLab v12.1 and up.
+ For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3", "192.168.0.1/24"]`.
- `maxretry`: The maximum amount of times a request can be made in the
- specified time.
+ specified time.
- `findtime`: The maximum amount of time that failed requests can count against an IP
- before it's blacklisted (in seconds).
+ before it's blacklisted (in seconds).
- `bantime`: The total amount of time that a blacklisted IP will be blocked (in
- seconds).
+ seconds).
**Installations from source**
@@ -71,18 +72,18 @@ taken in order to enable protection for your GitLab instance:
1. In `config/application.rb` find and uncomment the following line:
- ```ruby
- config.middleware.use Rack::Attack
- ```
+ ```ruby
+ config.middleware.use Rack::Attack
+ ```
1. Copy `config/initializers/rack_attack.rb.example` to `config/initializers/rack_attack.rb`
1. Open `config/initializers/rack_attack.rb`, review the
`paths_to_be_protected`, and add any other path you need protecting
1. Restart GitLab:
- ```sh
- sudo service gitlab restart
- ```
+ ```sh
+ sudo service gitlab restart
+ ```
If you want more restrictive/relaxed throttle rules, edit
`config/initializers/rack_attack.rb` and change the `limit` or `period` values.
@@ -98,28 +99,28 @@ In case you want to remove a blocked IP, follow these steps:
1. Find the IPs that have been blocked in the production log:
- ```sh
- grep "Rack_Attack" /var/log/gitlab/gitlab-rails/auth.log
- ```
+ ```sh
+ grep "Rack_Attack" /var/log/gitlab/gitlab-rails/auth.log
+ ```
1. Since the blacklist is stored in Redis, you need to open up `redis-cli`:
- ```sh
- /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket
- ```
+ ```sh
+ /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket
+ ```
1. You can remove the block using the following syntax, replacing `<ip>` with
the actual IP that is blacklisted:
- ```
- del cache:gitlab:rack::attack:allow2ban:ban:<ip>
- ```
+ ```
+ del cache:gitlab:rack::attack:allow2ban:ban:<ip>
+ ```
1. Confirm that the key with the IP no longer shows up:
- ```
- keys *rack::attack*
- ```
+ ```
+ keys *rack::attack*
+ ```
1. Optionally, add the IP to the whitelist to prevent it from being blacklisted
again (see [settings](#settings)).
@@ -136,8 +137,8 @@ the load balancer. In that case, you will need to:
1. Whitelist the load balancer's IP address(es) in the Rack Attack [settings](#settings).
1. Reconfigure GitLab:
- ```
- sudo gitlab-ctl reconfigure
- ```
+ ```
+ sudo gitlab-ctl reconfigure
+ ```
1. [Remove the block via Redis.](#remove-blocked-ips-from-rack-attack-via-redis)
diff --git a/doc/security/reset_root_password.md b/doc/security/reset_root_password.md
index a58d70f0ff2..ec360e2d338 100644
--- a/doc/security/reset_root_password.md
+++ b/doc/security/reset_root_password.md
@@ -1,6 +1,7 @@
---
type: howto
---
+
# How to reset your root password
To reset your root password, first log into your server with root privileges.
@@ -22,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/ssh_keys_restrictions.md b/doc/security/ssh_keys_restrictions.md
index ae4cc44519e..4c60daf77f4 100644
--- a/doc/security/ssh_keys_restrictions.md
+++ b/doc/security/ssh_keys_restrictions.md
@@ -1,6 +1,7 @@
---
type: reference, howto
---
+
# Restrict allowed SSH key technologies and minimum length
`ssh-keygen` allows users to create RSA keys with as few as 768 bits, which
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 49dadd5abc2..b08d9ffa26e 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -1,6 +1,7 @@
---
type: howto
---
+
# Enforce Two-factor Authentication (2FA)
Two-factor Authentication (2FA) provides an additional level of security to your
@@ -48,11 +49,11 @@ need to be administrator or owner of the group.
The following are important notes about 2FA:
- Projects belonging to a 2FA-enabled group that
- [is shared](../user/project/members/share_project_with_groups.md)
+ [is shared](../user/project/members/share_project_with_groups.md)
with a 2FA-disabled group will *not* require members of the 2FA-disabled group to use
- 2FA for the project. For example, if project *P* belongs to 2FA-enabled group *A* and
+ 2FA for the project. For example, if project *P* belongs to 2FA-enabled group *A* and
is shared with 2FA-disabled group *B*, members of group *B* can access project *P*
- without 2FA. To ensure this scenario doesn't occur,
+ without 2FA. To ensure this scenario doesn't occur,
[prevent sharing of projects](../user/group/index.md#share-with-group-lock)
for the 2FA-enabled group.
- If you add additional members to a project within a group or subgroup that has
diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md
index 75cf754e197..d34826c853c 100644
--- a/doc/security/unlock_user.md
+++ b/doc/security/unlock_user.md
@@ -1,38 +1,45 @@
---
type: howto
---
-# How to unlock a locked user
-To unlock a locked user, first log into your server with root privileges.
+# How to unlock a locked user from the command line
-Start a Ruby on Rails console with this command:
+After six failed login attempts a user gets in a locked state.
+To unlock a locked user:
-```bash
-gitlab-rails console production
-```
+1. SSH into your GitLab server.
+1. Start a Ruby on Rails console:
-Wait until the console has loaded.
+ ```sh
+ ## For Omnibus GitLab
+ sudo gitlab-rails console production
-There are multiple ways to find your user. You can search for email or username.
+ ## For installations from source
+ sudo -u git -H bundle exec rails console RAILS_ENV=production
+ ```
-```bash
-user = User.where(id: 1).first
-```
+1. Find the user to unlock. You can search by email or ID.
-or
+ ```ruby
+ user = User.find_by(email: 'admin@local.host')
+ ```
-```bash
-user = User.find_by(email: 'admin@local.host')
-```
+ or
-Unlock the user:
+ ```ruby
+ user = User.where(id: 1).first
+ ```
-```bash
-user.unlock_access!
-```
+1. Unlock the user:
-Exit the console, the user should now be able to log in again.
+ ```ruby
+ user.unlock_access!
+ ```
+
+1. Exit the console with <kbd>Ctrl</kbd>+<kbd>d</kbd>
+
+The user should now be able to log in.
<!-- ## Troubleshooting
diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md
index f0af0a7ac6a..7ba50acbb06 100644
--- a/doc/security/user_email_confirmation.md
+++ b/doc/security/user_email_confirmation.md
@@ -1,6 +1,7 @@
---
type: howto
---
+
# User email confirmation at sign-up
GitLab can be configured to require confirmation of a user's email address when
diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md
index 00a2607b607..9fc8f7ec985 100644
--- a/doc/security/user_file_uploads.md
+++ b/doc/security/user_file_uploads.md
@@ -1,6 +1,7 @@
---
type: reference
---
+
# User File Uploads
Images that are attached to issues, merge requests, or comments
@@ -12,7 +13,7 @@ image contains sensitive information.
Authentication is not enabled because images must be visible in the body of
notification emails, which are often read from email clients that are not
authenticated with GitLab, such as Outlook, Apple Mail, or the Mail app on your
-mobile device.
+mobile device.
>**Note:**
Non-image attachments do require authentication to be viewed.
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index d4fa088cb15..1194234a295 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -1,6 +1,7 @@
---
type: concepts, reference, howto
---
+
# Webhooks and insecure internal web services
If you have non-GitLab web services running on your GitLab server or within its
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index dbd9bcee935..e18f49de3f0 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -64,13 +64,13 @@ Following [best practices](https://linux-audit.com/using-ed25519-openssh-keys-in
you should always favor [ED25519](https://ed25519.cr.yp.to/) SSH keys, since they
are more secure and have better performance over the other types.
-ED25519 SSH keys were introduced in OpenSSH 6.5,
-so any modern OS should include the option to create them.
-If for any reason your OS or the GitLab instance you interact with doesn't
+ED25519 SSH keys were introduced in OpenSSH 6.5,
+so any modern OS should include the option to create them.
+If for any reason your OS or the GitLab instance you interact with doesn't
support ED25519, you can fallback to RSA.
NOTE: **Note:**
-Omnibus does not ship with OpenSSH, so it uses the version on your GitLab server. If using
+Omnibus does not ship with OpenSSH, so it uses the version on your GitLab server. If using
Omnibus, ensure the version of OpenSSH installed is version 6.5 or newer if you want to use ED25519 SSH keys.
### RSA SSH keys
@@ -107,18 +107,18 @@ To create a new SSH key pair:
1. Open a terminal on Linux or macOS, or Git Bash / WSL on Windows.
1. Generate a new ED25519 SSH key pair:
- ```bash
- ssh-keygen -t ed25519 -C "email@example.com"
- ```
+ ```bash
+ ssh-keygen -t ed25519 -C "email@example.com"
+ ```
- Or, if you want to use RSA:
+ Or, if you want to use RSA:
- ```bash
- ssh-keygen -o -t rsa -b 4096 -C "email@example.com"
- ```
+ ```bash
+ ssh-keygen -o -t rsa -b 4096 -C "email@example.com"
+ ```
- The `-C` flag adds a comment in the key in case you have multiple of them
- and want to tell which is which. It is optional.
+ The `-C` flag adds a comment in the key in case you have multiple of them
+ and want to tell which is which. It is optional.
1. Next, you will be prompted to input a file path to save your SSH key pair to.
If you don't already have an SSH key pair and aren't generating a [deploy key](#deploy-keys),
@@ -126,21 +126,21 @@ To create a new SSH key pair:
<kbd>Enter</kbd>. Using the suggested path will normally allow your SSH client
to automatically use the SSH key pair with no additional configuration.
- If you already have an SSH key pair with the suggested file path, you will need
- to input a new file path and [declare what host](#working-with-non-default-ssh-key-pair-paths)
- this SSH key pair will be used for in your `~/.ssh/config` file.
+ If you already have an SSH key pair with the suggested file path, you will need
+ to input a new file path and [declare what host](#working-with-non-default-ssh-key-pair-paths)
+ this SSH key pair will be used for in your `~/.ssh/config` file.
1. Once the path is decided, you will be prompted to input a password to
secure your new SSH key pair. It's a best practice to use a password,
but it's not required and you can skip creating it by pressing
<kbd>Enter</kbd> twice.
- If, in any case, you want to add or change the password of your SSH key pair,
- you can use the `-p` flag:
+ If, in any case, you want to add or change the password of your SSH key pair,
+ you can use the `-p` flag:
- ```
- ssh-keygen -p -o -f <keyname>
- ```
+ ```
+ ssh-keygen -p -o -f <keyname>
+ ```
Now, it's time to add the newly created public key to your GitLab account.
@@ -149,41 +149,40 @@ Now, it's time to add the newly created public key to your GitLab account.
1. Copy your **public** SSH key to the clipboard by using one of the commands below
depending on your Operating System:
- **macOS:**
+ **macOS:**
- ```bash
- pbcopy < ~/.ssh/id_ed25519.pub
- ```
+ ```bash
+ pbcopy < ~/.ssh/id_ed25519.pub
+ ```
- **WSL / GNU/Linux (requires the xclip package):**
+ **WSL / GNU/Linux (requires the xclip package):**
- ```bash
- xclip -sel clip < ~/.ssh/id_ed25519.pub
- ```
+ ```bash
+ xclip -sel clip < ~/.ssh/id_ed25519.pub
+ ```
- **Git Bash on Windows:**
+ **Git Bash on Windows:**
- ```bash
- cat ~/.ssh/id_ed25519.pub | clip
- ```
+ ```bash
+ cat ~/.ssh/id_ed25519.pub | clip
+ ```
- You can also open the key in a graphical editor and copy it from there,
- but be careful not to accidentally change anything.
+ You can also open the key in a graphical editor and copy it from there,
+ but be careful not to accidentally change anything.
- NOTE: **Note:**
- If you opted to create an RSA key, the name might differ.
+ NOTE: **Note:**
+ If you opted to create an RSA key, the name might differ.
1. Add your **public** SSH key to your GitLab account by:
1. Clicking your avatar in the upper right corner and selecting **Settings**.
1. Navigating to **SSH Keys** and pasting your **public** key in the **Key** field. If you:
-
- Created the key with a comment, this will appear in the **Title** field.
- Created the key without a comment, give your key an identifiable title like _Work Laptop_ or _Home Workstation_.
1. Click the **Add key** button.
- NOTE: **Note:**
- If you manually copied your public SSH key make sure you copied the entire
- key starting with `ssh-ed25519` (or `ssh-rsa`) and ending with your email.
+ NOTE: **Note:**
+ If you manually copied your public SSH key make sure you copied the entire
+ key starting with `ssh-ed25519` (or `ssh-rsa`) and ending with your email.
## Testing that everything is set up correctly
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index 745a2253e84..68e62fff106 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -69,6 +69,14 @@ Once your GitLab.com account is linked, you can go to your [Subscriptions](https
Please note that you need to be a group owner to associate a group to your subscription.
+### GitLab.com: Upgrade your subscription plan
+
+GitLab.com subscriptions can be upgraded directly through the [Subscriptions portal](https://customers.gitlab.com/subscriptions).
+
+The Subscriptions portal provides an **Upgrade** button below each GitLab.com
+subscription, which will lead you to a simple
+checkout process.
+
### Confirm or upgrade your GitLab.com subscription details within GitLab
To see the status of your GitLab.com subscription, you can click on the Billings
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index a46f7d30892..e8bd35fba5c 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -330,6 +330,7 @@ If the user is blocked via LDAP, `state` will be `ldap_blocked`.
"user_id": 41
}
```
+
**Group Member Removed:**
```json
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index e2d51f673b5..30b9e88ade8 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -109,10 +109,10 @@ To make full use of Auto DevOps, you will need:
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. **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
@@ -168,7 +168,6 @@ From GitLab 11.8, `KUBE_INGRESS_BASE_DOMAIN` replaces `AUTO_DEVOPS_DOMAIN`.
Support for `AUTO_DEVOPS_DOMAIN` was [removed in GitLab
12.0](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959).
-
## Using multiple Kubernetes clusters **(PREMIUM)**
When using Auto DevOps, you may want to deploy different environments to
@@ -202,7 +201,7 @@ To add a different cluster for each environment:
1. Navigate to your project's **Operations > Kubernetes** and create the Kubernetes clusters
with their respective environment scope as described from the table above.
- ![Auto DevOps multiple clusters](img/autodevops_multiple_clusters.png)
+ ![Auto DevOps multiple clusters](img/autodevops_multiple_clusters.png)
1. After the clusters are created, navigate to each one and install Helm Tiller
and Ingress. Wait for the Ingress IP address to be assigned.
@@ -223,7 +222,7 @@ full use of Auto DevOps are available. If this is your fist time, we recommend y
[quick start guide](quick_start_guide.md).
GitLab.com users can enable/disable Auto DevOps at the project-level only. Self-managed users
-can enable/disable Auto DevOps at either the project-level or instance-level.
+can enable/disable Auto DevOps at the project-level, group-level or instance-level.
### Enabling/disabling Auto DevOps at the instance-level (Administrators only)
@@ -316,7 +315,7 @@ If a project's repository contains a `Dockerfile`, Auto Build will use
If you are also using Auto Review Apps and Auto Deploy and choose to provide
your own `Dockerfile`, make sure you expose your application to port
`5000` as this is the port assumed by the
-[default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app).
+[default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app). Alternatively you can override the default values by [customizing the Auto Deploy helm chart](#custom-helm-chart)
#### Auto Build using Heroku buildpacks
@@ -452,7 +451,7 @@ be deleted.
Review apps are deployed using the
[auto-deploy-app](https://gitlab.com/gitlab-org/charts/auto-deploy-app) chart with
-Helm. The app will be deployed into the [Kubernetes
+Helm, which can be [customized](#custom-helm-chart). The app will be deployed into the [Kubernetes
namespace](../../user/project/clusters/index.md#deployment-variables)
for the environment.
@@ -514,7 +513,7 @@ Auto Deploy doesn't include deployments to staging or canary by default, but the
enable them.
You can make use of [environment variables](#environment-variables) to automatically
-scale your pod replicas.
+scale your pod replicas and to apply custom arguments to the Auto DevOps `helm upgrade` commands. This is an easy way to [customize the Auto Deploy helm chart](#custom-helm-chart).
Apps are deployed using the
[auto-deploy-app](https://gitlab.com/gitlab-org/charts/auto-deploy-app) chart with
@@ -655,6 +654,9 @@ repo or by specifying a project variable:
- **Project variable** - Create a [project variable](../../ci/variables/README.md#gitlab-cicd-environment-variables)
`AUTO_DEVOPS_CHART` with the URL of a custom chart to use or create two project variables `AUTO_DEVOPS_CHART_REPOSITORY` with the URL of a custom chart repository and `AUTO_DEVOPS_CHART` with the path to the chart.
+You can also make use of the `HELM_UPGRADE_EXTRA_ARGS` environment variable to override the default values in the `values.yaml` file in the [default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app).
+To apply your own `values.yaml` file to all Helm upgrade commands in Auto Deploy set `HELM_UPGRADE_EXTRA_ARGS` to `--values my-values.yaml`.
+
### Custom Helm chart per environment **(PREMIUM)**
You can specify the use of a custom Helm chart per environment by scoping the environment variable
@@ -761,7 +763,8 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. |
| `ROLLOUT_RESOURCE_TYPE` | From GitLab 11.9, this variable allows specification of the resource type being deployed when using a custom helm chart. Default value is `deployment`. |
| `ROLLOUT_STATUS_DISABLED` | From GitLab 12.0, this variable allows to disable rollout status check because it doesn't support all resource types, for example, `cronjob`. |
-| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, this variable allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. |
+| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, this variable allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. **Tip:** you can use this variable to [customize the Auto Deploy helm chart](https://docs.gitlab.com/ee/topics/autodevops/index.html#custom-helm-chart) by applying custom override values with `--values my-values.yaml`. |
+| `HELM_RELEASE_NAME` | From GitLab 12.1, this variable allows the `helm` release name to be overridden, this can be used to assign unique release names when deploying multiple projects to a single namespace |
TIP: **Tip:**
Set up the replica variables using a
@@ -787,11 +790,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`.
-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
diff --git a/doc/university/training/topics/env_setup.md b/doc/university/training/topics/env_setup.md
index 305f5ecb1fb..92d2613c5d2 100644
--- a/doc/university/training/topics/env_setup.md
+++ b/doc/university/training/topics/env_setup.md
@@ -14,9 +14,11 @@ comments: false
- If it's not installed, it will prompt you to install it.
- **Linux**
+
```bash
sudo yum install git-all
```
+
```bash
sudo apt-get install git-all
```
diff --git a/doc/university/training/topics/getting_started.md b/doc/university/training/topics/getting_started.md
index 08027c5d15b..e8ff7916590 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
diff --git a/doc/user/admin_area/geo_nodes.md b/doc/user/admin_area/geo_nodes.md
index 9e7057f93d4..39753fd885e 100644
--- a/doc/user/admin_area/geo_nodes.md
+++ b/doc/user/admin_area/geo_nodes.md
@@ -64,8 +64,8 @@ Internal URL defaults to External URL, but you can customize it under
CAUTION: **Warning:**
We recommend using an HTTPS connection while configuring the Geo nodes. To avoid
breaking communication between **primary** and **secondary** nodes when using
-HTTPS, customize your Internal URL to point to a Load Balancer with TLS
-termination.
+HTTPS, customize your Internal URL to point to a load balancer with TLS
+terminated at the load balancer.
## Multiple secondary nodes behind a load balancer
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 9968b7349dc..7fbb4d84cfc 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -41,7 +41,7 @@ These settings can be found within:
- The path `/admin/application_settings`.
The first push of a new project, including LFS objects, will be checked for size
-and **will** be rejected if the sum of their sizes exceeds the maximum allowed
+and **will** be rejected if the sum of their sizes exceeds the maximum allowed
repository size.
**Note:** The repository size limit includes repository files and LFS, and does not include artifacts.
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index ebbb2472752..e56acac527f 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -94,7 +94,7 @@ 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]**
+## 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
@@ -164,3 +164,23 @@ 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. -->
+
+## Required pipeline configuration **(PREMIUM ONLY)**
+
+GitLab administrators can force a pipeline configuration to run on every
+pipeline.
+
+The configuration applies to all pipelines for a GitLab instance and is
+sourced from:
+
+- The [instance template repository](instance_template_repository.md).
+- GitLab-supplied configuration.
+
+To set required pipeline configuration:
+
+1. Go to **Admin area > Settings > CI/CD**.
+1. Expand the **Required pipeline configuration** section.
+1. Select the required configuration from the provided dropdown.
+1. Click **Save changes**.
+
+![Required pipeline](img/admin_required_pipeline.png)
diff --git a/doc/user/admin_area/settings/img/admin_required_pipeline.png b/doc/user/admin_area/settings/img/admin_required_pipeline.png
new file mode 100644
index 00000000000..58488674d51
--- /dev/null
+++ b/doc/user/admin_area/settings/img/admin_required_pipeline.png
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..4d888da3416
--- /dev/null
+++ b/doc/user/admin_area/settings/img/email_confirmation.png
Binary files differ
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index cebf36c7ec1..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
-You can block email addresses of specific domains, or whitelist only some
-specific domains via the **Application Settings** in the Admin area.
+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,17 +31,23 @@ 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
-This feature can be activated via the **Application Settings** in the Admin area,
-and you have the option of entering the list manually, or uploading a file with
-the list.
+To access this feature:
+
+1. Navigate to the **Settings > General** in the Admin area.
+1. Expand the **Sign-up restrictions** section.
+
+For the blacklist, you can enter the list manually or upload a `.txt` file that
+contains list entries.
+
+For the whitelist, you must enter the list manually.
-Both whitelist and blacklist accept wildcards, so for example, you can use
+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
domains ending in `.io`. Domains should be separated by a whitespace,
semicolon, comma, or a new line.
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 696446599c8..da75684a3fe 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -10,7 +10,7 @@ images (or more precisely the containers) for known vulnerabilities by using
[Clair](https://github.com/coreos/clair) and [clair-scanner](https://github.com/arminc/clair-scanner),
two open source tools for Vulnerability Static Analysis for containers.
-You can take advantage of Container Scanning by either [including the CI job](#including-the-provided-template) in
+You can take advantage of Container Scanning by either [including the CI job](#configuration) in
your existing `.gitlab-ci.yml` file or by implicitly using
[Auto Container Scanning](../../../topics/autodevops/index.md#auto-container-scanning-ultimate)
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
@@ -55,32 +55,16 @@ To enable Container Scanning in your pipeline, you need:
[predefined environment variables](../../../ci/variables/predefined_variables.md)
document.
-## Configuring Container Scanning
+## Configuration
-To enable Container Scanning in your project, define a job in your
-`.gitlab-ci.yml` file that generates the
-[Container Scanning report artifact](../../../ci/yaml/README.md#artifactsreportscontainer_scanning-ultimate).
+For GitLab 11.9 and later, to enable Container Scanning, you must
+[include](../../../ci/yaml/README.md#includetemplate) the
+[`Container-Scanning.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml)
+that's provided as a part of your GitLab installation.
+For GitLab versions earlier than 11.9, you can copy and use the job as defined
+in that template.
-This can be done in two ways:
-
-- For GitLab 11.9 and later, including the provided
- `Container-Scanning.gitlab-ci.yml` template (recommended).
-- Manually specifying the job definition. Not recommended unless using GitLab
- 11.8 and earlier.
-
-### Including the provided template
-
-NOTE: **Note:**
-The CI/CD Container Scanning template is supported on GitLab 11.9 and later versions.
-For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
-
-A CI/CD [Container Scanning template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml)
-with the default Container Scanning job definition is provided as a part of your GitLab
-installation that you can [include](../../../ci/yaml/README.md#includetemplate)
-in your `.gitlab-ci.yml` file.
-
-To enable Container Scanning using the provided template, add the following to
-your `.gitlab-ci.yml` file:
+Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
@@ -89,12 +73,12 @@ include:
The included template will:
-- Create a `container_scanning` job in your CI/CD pipeline.
-- Pull the already built Docker image from your project's
- [Container Registry](../../project/container_registry.md) (see [requirements](#requirements))
- and scan it for possible vulnerabilities.
+1. Create a `container_scanning` job in your CI/CD pipeline.
+1. Pull the already built Docker image from your project's
+ [Container Registry](../../project/container_registry.md) (see [requirements](#requirements))
+ and scan it for possible vulnerabilities.
-The report will be saved as a
+The results will be saved as a
[Container Scanning report artifact](../../../ci/yaml/README.md#artifactsreportscontainer_scanning-ultimate)
that you can later download and analyze.
Due to implementation limitations, we always take the latest Container Scanning
@@ -106,95 +90,6 @@ If you want to whitelist some specific vulnerabilities, you can do so by definin
them in a YAML file named `clair-whitelist.yml`. Read more in the
[Clair documentation](https://github.com/arminc/clair-scanner/blob/master/README.md#example-whitelist-yaml-file).
-### Manual job definition for GitLab 11.5 and later
-
-CAUTION: **Caution:**
-The job definition shown below is supported on GitLab 11.5 and later versions.
-However, if you're using GitLab 11.9+, it's recommended to use
-[the provided Container Scanning template](#including-the-provided-template).
-
-For GitLab 11.5 and GitLab Runner 11.5 and later, the following `container_scanning`
-job can be added:
-
-```yaml
-container_scanning:
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- ## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables
- CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
- CI_APPLICATION_TAG: $CI_COMMIT_SHA
- CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - docker run -d --name db arminc/clair-db:latest
- - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION}
- - apk add -U wget ca-certificates
- - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- - mv clair-scanner_linux_amd64 clair-scanner
- - chmod +x clair-scanner
- - touch clair-whitelist.yml
- - while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
- - retries=0
- - echo "Waiting for clair daemon to start"
- - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
- - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
- artifacts:
- reports:
- container_scanning: gl-container-scanning-report.json
-```
-
-### Manual job definition for GitLab 11.4 and earlier (deprecated)
-
-CAUTION: **Deprecated:**
-Before GitLab 11.5, the Container Scanning job and artifact had to be named specifically
-to automatically extract report data and show it in the merge request widget.
-While these old job definitions are still maintained, they have been deprecated
-and may be removed in the next major release, GitLab 12.0. You are strongly
-advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
-
-For GitLab 11.4 and earlier, the Container Scanning job should look like:
-
-```yaml
-container_scanning:
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- ## Define two new variables based on GitLab's CI/CD predefined variables
- ## https://docs.gitlab.com/ee/ci/variables/README.html#predefined-environment-variables
- CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
- CI_APPLICATION_TAG: $CI_COMMIT_SHA
- CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - docker run -d --name db arminc/clair-db:latest
- - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION}
- - apk add -U wget ca-certificates
- - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
- - mv clair-scanner_linux_amd64 clair-scanner
- - chmod +x clair-scanner
- - touch clair-whitelist.yml
- - while( ! wget -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; done
- - retries=0
- - echo "Waiting for clair daemon to start"
- - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done
- - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
- artifacts:
- paths: [gl-container-scanning-report.json]
-```
-
-Alternatively, the job name could be `sast:container`
-and the artifact name could be `gl-sast-container-report.json`.
-These names have been deprecated with GitLab 11.0
-and may be removed in the next major release, GitLab 12.0.
-
## Security Dashboard
The Security Dashboard is a good place to get an overview of all the security
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index 936703cce32..4b98dd73d76 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -14,7 +14,7 @@ Dynamic Application Security Testing (DAST) comes into place.
If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your running web application(s)
for known vulnerabilities using Dynamic Application Security Testing (DAST).
-You can take advantage of DAST by either [including the CI job](#configuring-dast) in
+You can take advantage of DAST by either [including the CI job](#configuration) in
your existing `.gitlab-ci.yml` file or by implicitly using
[Auto DAST](../../../topics/autodevops/index.md#auto-dast-ultimate)
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
@@ -51,30 +51,16 @@ applications while you are developing and testing your applications.
To run a DAST job, you need GitLab Runner with the
[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
-## Configuring DAST
+## Configuration
-To enable DAST in your project, define a job in your `.gitlab-ci.yml` file that generates the
-[DAST report artifact](../../../ci/yaml/README.md#artifactsreportsdast-ultimate).
+For GitLab 11.9 and later, to enable DAST, you must
+[include](../../../ci/yaml/README.md#includetemplate) the
+[`DAST.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml)
+that's provided as a part of your GitLab installation.
+For GitLab versions earlier than 11.9, you can copy and use the job as defined
+in that template.
-This can be done in two ways:
-
-- For GitLab 11.9 and later, including the provided `DAST.gitlab-ci.yml` template (recommended).
-- Manually specifying the job definition. Not recommended unless using GitLab
- 11.8 and earlier.
-
-### Including the provided template
-
-NOTE: **Note:**
-The CI/CD DAST template is supported on GitLab 11.9 and later versions.
-For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
-
-A CI/CD [DAST template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml)
-with the default DAST job definition is provided as a part of your GitLab
-installation which you can [include](../../../ci/yaml/README.md#includetemplate)
-in your `.gitlab-ci.yml` file.
-
-To enable DAST using the provided template, add the following to your `.gitlab-ci.yml`
-file:
+Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
@@ -84,22 +70,22 @@ variables:
DAST_WEBSITE: https://example.com
```
+There are two ways to define the URL to be scanned by DAST:
+
+- Set the `DAST_WEBSITE` [variable](../../../ci/yaml/README.md#variables).
+- Add it in an `environment_url.txt` file at the root of your project.
+
The included template will create a `dast` job in your CI/CD pipeline and scan
your project's source code for possible vulnerabilities.
-The report will be saved as a
+The results will be saved as a
[DAST report artifact](../../../ci/yaml/README.md#artifactsreportsdast-ultimate)
that you can later download and analyze. Due to implementation limitations we
always take the latest DAST artifact available. Behind the scenes, the
[GitLab DAST Docker image](https://gitlab.com/gitlab-org/security-products/dast)
is used to run the tests on the specified URL and scan it for possible vulnerabilities.
-There are two ways to define the URL to be scanned by DAST:
-
-- Set the `DAST_WEBSITE` [variable](../../../ci/yaml/README.md#variables).
-- Add it in an `environment_url.txt` file at the root of your project.
-
-#### Authenticated scan
+### Authenticated scan
It's also possible to authenticate the user before performing the DAST checks:
@@ -117,12 +103,12 @@ variables:
DAST_AUTH_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
```
-The report will be saved as a
+The results will be saved as a
[DAST report artifact](../../../ci/yaml/README.md#artifactsreportsdast-ultimate)
that you can later download and analyze.
Due to implementation limitations, we always take the latest DAST artifact available.
-#### Full scan
+### Full scan
DAST can be configured to perform [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan), which
includes both passive and active scanning against the same target website:
@@ -135,7 +121,7 @@ variables:
DAST_FULL_SCAN_ENABLED: "true"
```
-#### Customizing the DAST settings
+### Customizing the DAST settings
The DAST settings can be changed through environment variables by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
@@ -155,7 +141,7 @@ variables:
Because the template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline
configuration, the last mention of the variable will take precedence.
-#### Overriding the DAST template
+### Overriding the DAST template
If you want to override the job definition (for example, change properties like
`variables` or `dependencies`), you need to declare a `dast` job after the
@@ -176,78 +162,27 @@ 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.
-### Manual job definition for GitLab 11.5 and later
-
-For GitLab 11.5 and GitLab Runner 11.5 and later, the following `dast`
-job can be added:
-
-```yaml
-dast:
- image: registry.gitlab.com/gitlab-org/security-products/zaproxy
- variables:
- website: "https://example.com"
- allow_failure: true
- script:
- - mkdir /zap/wrk/
- - /zap/zap-baseline.py -J gl-dast-report.json -t $website || true
- - cp /zap/wrk/gl-dast-report.json .
- artifacts:
- reports:
- dast: gl-dast-report.json
-```
-
-Where the `website` variable holds the URL to run the tests against.
-
-For an authenticated scan, use the following definition:
-
-```yaml
-dast:
- image: registry.gitlab.com/gitlab-org/security-products/zaproxy
- variables:
- website: "https://example.com"
- login_url: "https://example.com/sign-in"
- username: "john.doe@example.com"
- password: "john-doe-password"
- allow_failure: true
- script:
- - mkdir /zap/wrk/
- - /zap/zap-baseline.py -J gl-dast-report.json -t $website
- --auth-url $login_url
- --auth-username $username
- --auth-password $password || true
- - cp /zap/wrk/gl-dast-report.json .
- artifacts:
- reports:
- dast: gl-dast-report.json
-```
-
-See the [zaproxy documentation](https://gitlab.com/gitlab-org/security-products/zaproxy)
-to learn more about the authentication settings.
-
-### Manual job definition for GitLab 11.4 and earlier (deprecated)
-
-CAUTION: **Caution:**
-Before GitLab 11.5, DAST job and artifact had to be named specifically
-to automatically extract report data and show it in the merge request widget.
-While these old job definitions are still maintained they have been deprecated
-and may be removed in next major release, GitLab 12.0. You are strongly advised
-to update your current `.gitlab-ci.yml` configuration to reflect that change.
-
-For GitLab 11.4 and earlier, the job should look like:
-
-```yaml
-dast:
- image: registry.gitlab.com/gitlab-org/security-products/zaproxy
- variables:
- website: "https://example.com"
- allow_failure: true
- script:
- - mkdir /zap/wrk/
- - /zap/zap-baseline.py -J gl-dast-report.json -t $website || true
- - cp /zap/wrk/gl-dast-report.json .
- artifacts:
- paths: [gl-dast-report.json]
-```
+## 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
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 2fe8a6f9029..7473647f129 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -8,7 +8,7 @@ in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.7.
If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your dependencies for known
vulnerabilities using Dependency Scanning.
-You can take advantage of Dependency Scanning by either [including the CI job](#including-the-provided-template)
+You can take advantage of Dependency Scanning by either [including the CI job](#configuration)
in your existing `.gitlab-ci.yml` file or by implicitly using
[Auto Dependency Scanning](../../../topics/autodevops/index.md#auto-dependency-scanning-ultimate)
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
@@ -74,31 +74,16 @@ The Gemnasium client does **NOT** send the exact package versions your project r
You can disable the remote checks by [using](#customizing-the-dependency-scanning-settings)
the `DS_DISABLE_REMOTE_CHECKS` environment variable and setting it to `true`.
-## Configuring Dependency Scanning
+## Configuration
-To enable Dependency Scanning in your project, define a job in your `.gitlab-ci.yml`
-file that generates the
-[Dependency Scanning report artifact](../../../ci/yaml/README.md#artifactsreportsdependency_scanning-ultimate).
+For GitLab 11.9 and later, to enable Dependency Scanning, you must
+[include](../../../ci/yaml/README.md#includetemplate) the
+[`Dependency-Scanning.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml)
+that's provided as a part of your GitLab installation.
+For GitLab versions earlier than 11.9, you can copy and use the job as defined
+that template.
-This can be done in two ways:
-
-- For GitLab 11.9 and later, including the provided `Dependency-Scanning.gitlab-ci.yml` template (recommended).
-- Manually specifying the job definition. Not recommended unless using GitLab
- 11.8 and earlier.
-
-### Including the provided template
-
-NOTE: **Note:**
-The CI/CD Dependency Scanning template is supported on GitLab 11.9 and later versions.
-For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
-
-A CI/CD [Dependency Scanning template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml)
-with the default Dependency Scanning job definition is provided as a part of your GitLab
-installation which you can [include](../../../ci/yaml/README.md#includetemplate)
-in your `.gitlab-ci.yml` file.
-
-To enable Dependency Scanning using the provided template, add the following to
-your `.gitlab-ci.yml` file:
+Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
@@ -108,12 +93,12 @@ include:
The included template will create a `dependency_scanning` job in your CI/CD
pipeline and scan your project's source code for possible vulnerabilities.
-The report will be saved as a
+The results will be saved as a
[Dependency Scanning report artifact](../../../ci/yaml/README.md#artifactsreportsdependency_scanning-ultimate)
that you can later download and analyze. Due to implementation limitations, we
always take the latest Dependency Scanning artifact available.
-#### Customizing the Dependency Scanning settings
+### Customizing the Dependency Scanning settings
The Dependency Scanning settings can be changed through [environment variables](#available-variables) by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
@@ -131,7 +116,7 @@ variables:
Because template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline
configuration, the last mention of the variable will take precedence.
-#### Overriding the Dependency Scanning template
+### Overriding the Dependency Scanning template
If you want to override the job definition (for example, change properties like
`variables` or `dependencies`), you need to declare a `dependency_scanning` job
@@ -146,7 +131,7 @@ dependency_scanning:
CI_DEBUG_TRACE: "true"
```
-#### Available variables
+### Available variables
Dependency Scanning can be [configured](#customizing-the-dependency-scanning-settings)
using environment variables.
@@ -156,6 +141,8 @@ using environment variables.
| `DS_ANALYZER_IMAGES` | Comma separated list of custom images. The official default images are still enabled. Read more about [customizing analyzers](analyzers.md). |
| `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). |
@@ -163,82 +150,8 @@ 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`. |
-
-### Manual job definition for GitLab 11.5 and later
-
-For GitLab 11.5 and GitLab Runner 11.5 and later, the following `dependency_scanning`
-job can be added:
-
-```yaml
-dependency_scanning:
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - export DS_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
- - |
- docker run \
- --env DS_ANALYZER_IMAGES \
- --env DS_ANALYZER_IMAGE_PREFIX \
- --env DS_ANALYZER_IMAGE_TAG \
- --env DS_DEFAULT_ANALYZERS \
- --env DEP_SCAN_DISABLE_REMOTE_CHECKS \
- --env DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
- --env DS_PULL_ANALYZER_IMAGE_TIMEOUT \
- --env DS_RUN_ANALYZER_TIMEOUT \
- --volume "$PWD:/code" \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_VERSION" /code
- dependencies: []
- artifacts:
- reports:
- dependency_scanning: gl-dependency-scanning-report.json
-```
-
-You can supply many other [settings variables](#available-variables)
-via `docker run --env` to customize your job execution.
-
-### Manual job definition for GitLab 11.4 and earlier (deprecated)
-
-CAUTION: **Caution:**
-Before GitLab 11.5, the Dependency Scanning job and artifact had to be named specifically
-to automatically extract the report data and show it in the merge request widget.
-While these old job definitions are still maintained, they have been deprecated
-and may be removed in the next major release, GitLab 12.0. You are strongly advised
-to update your current `.gitlab-ci.yml` configuration to reflect that change.
-
-For GitLab 11.4 and earlier, the job should look like:
-
-```yaml
-dependency_scanning:
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - export DS_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
- - |
- docker run \
- --env DS_ANALYZER_IMAGES \
- --env DS_ANALYZER_IMAGE_PREFIX \
- --env DS_ANALYZER_IMAGE_TAG \
- --env DS_DEFAULT_ANALYZERS \
- --env DS_EXCLUDED_PATHS \
- --env DEP_SCAN_DISABLE_REMOTE_CHECKS \
- --env DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
- --env DS_PULL_ANALYZER_IMAGE_TIMEOUT \
- --env DS_RUN_ANALYZER_TIMEOUT \
- --volume "$PWD:/code" \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$DS_VERSION" /code
- artifacts:
- paths: [gl-dependency-scanning-report.json]
-```
+| `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
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 91e79f6c23b..56a4cbd26d2 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -115,9 +115,9 @@ 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)
diff --git a/doc/user/application_security/license_management/index.md b/doc/user/application_security/license_management/index.md
index 8eb231f8359..b0eb753938b 100644
--- a/doc/user/application_security/license_management/index.md
+++ b/doc/user/application_security/license_management/index.md
@@ -8,7 +8,7 @@ in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
If you are using [GitLab CI/CD](../../../ci/README.md), you can search your project dependencies for their licenses
using License Management.
-You can take advantage of License Management by either [including the job](#configuring-license-management)
+You can take advantage of License Management by either [including the job](#configuration)
in your existing `.gitlab-ci.yml` file or by implicitly using
[Auto License Management](../../../topics/autodevops/index.md#auto-license-management-ultimate)
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
@@ -65,33 +65,16 @@ The following languages and package managers are supported.
To run a License Management scanning job, you need GitLab Runner with the
[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
-## Configuring License Management
+## Configuration
-To enable License Management in your project, define a job in your `.gitlab-ci.yml`
-file that generates the [License Management report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate).
+For GitLab 11.9 and later, to enable License Management, you must
+[include](../../../ci/yaml/README.md#includetemplate) the
+[`License-Management.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml)
+that's provided as a part of your GitLab installation.
+For GitLab versions earlier than 11.9, you can copy and use the job as defined
+that template.
-This can be done in two ways:
-
-- For GitLab 11.9 and later, including the provided `License-Management.gitlab-ci.yml` template (recommended).
-- Manually specifying the job definition. Not recommended unless using GitLab
- 11.8 and earlier.
-
-The License Management settings can be changed through environment variables by using the
-[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [License Management documentation](https://gitlab.com/gitlab-org/security-products/license-management#settings).
-
-### Including the provided template
-
-NOTE: **Note:**
-The CI/CD License Management template is supported on GitLab 11.9 and later versions.
-For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
-
-A CI/CD [License Management template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml)
-with the default License Management job definition is provided as a part of your GitLab
-installation which you can [include](../../../ci/yaml/README.md#includetemplate)
-in your `.gitlab-ci.yml` file.
-
-To enable License Management using the provided template, add the following to
-your `.gitlab-ci.yml` file:
+Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
@@ -101,14 +84,17 @@ include:
The included template will create a `license_management` job in your CI/CD pipeline
and scan your dependencies to find their licenses.
-The report will be saved as a
+The results will be saved as a
[License Management report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate)
that you can later download and analyze. Due to implementation limitations, we
always take the latest License Management artifact available. Behind the scenes, the
[GitLab License Management Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
is used to detect the languages/frameworks and in turn analyzes the licenses.
-#### Installing custom dependencies
+The License Management settings can be changed through environment variables by using the
+[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [License Management documentation](https://gitlab.com/gitlab-org/security-products/license-management#settings).
+
+### Installing custom dependencies
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
@@ -136,7 +122,7 @@ variables:
In this example, `my-custom-install-script.sh` is a shell script at the root
directory of your project.
-#### Overriding the template
+### Overriding the template
If you want to override the job definition (for example, change properties like
`variables` or `dependencies`), you need to declare a `license_management` job
@@ -151,7 +137,7 @@ license_management:
CI_DEBUG_TRACE: "true"
```
-#### Configuring Maven projects
+### Configuring Maven projects
The License Management tool provides a `MAVEN_CLI_OPTS` environment variable which can hold
the command line arguments to pass to the `mvn install` command which is executed under the hood.
@@ -192,67 +178,6 @@ license_management:
LM_PYTHON_VERSION: 3
```
-### Manual job definition for GitLab 11.5 and later
-
-For GitLab 11.5 and GitLab Runner 11.5 and later, the following `license_management`
-job can be added:
-
-```yaml
-license_management:
- image:
- name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
- entrypoint: [""]
- stage: test
- allow_failure: true
- script:
- - /run.sh analyze .
- artifacts:
- reports:
- license_management: gl-license-management-report.json
-```
-
-If you want to install custom project dependencies via the `SETUP_CMD` variable:
-
-```yaml
-license_management:
- image:
- name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
- entrypoint: [""]
- stage: test
- variables:
- SETUP_CMD: ./my-custom-install-script.sh
- allow_failure: true
- script:
- - /run.sh analyze .
- artifacts:
- reports:
- license_management: gl-license-management-report.json
-```
-
-### Manual job definition for GitLab 11.4 and earlier (deprecated)
-
-CAUTION: **Caution:**
-Before GitLab 11.5, the License Management job and artifact had to be named specifically
-to automatically extract the report data and show it in the merge request widget.
-While these old job definitions are still maintained, they have been deprecated
-and may be removed in the next major release, GitLab 12.0. You are strongly advised
-to update your current `.gitlab-ci.yml` configuration to reflect that change.
-
-For GitLab 11.4 and earlier, the job should look like:
-
-```yaml
-license_management:
- image:
- name: "registry.gitlab.com/gitlab-org/security-products/license-management:$CI_SERVER_VERSION_MAJOR-$CI_SERVER_VERSION_MINOR-stable"
- entrypoint: [""]
- stage: test
- allow_failure: true
- script:
- - /run.sh analyze .
- artifacts:
- paths: [gl-license-management-report.json]
-```
-
## Project policies for License Management
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5940)
@@ -271,16 +196,15 @@ To approve or blacklist a license:
1. Click the **Add a license** button.
![License Management Add License](img/license_management_add_license.png)
+
1. In the **License name** dropdown, either:
- - Select one of the available licenses. You can search for licenses in the field
- at the top of the list.
- - Enter arbitrary text in the field at the top of the list. This will cause the text to be
- added as a license name to the list.
+ - Select one of the available licenses. You can search for licenses in the field
+ at the top of the list.
+ - Enter arbitrary text in the field at the top of the list. This will cause the text to be
+ added as a license name to the list.
1. Select the **Approve** or **Blacklist** radio button to approve or blacklist respectively
the selected license.
-
-
To modify an existing license:
1. In the **License Management** list, click the **Approved/Declined** dropdown to change it to the desired status.
@@ -293,8 +217,6 @@ Searching for Licenses:
![License Management Search](img/license_management_search.png)
-
-
## License Management report under pipelines
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5491)
diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md
new file mode 100644
index 00000000000..59835aeba01
--- /dev/null
+++ b/doc/user/application_security/sast/analyzers.md
@@ -0,0 +1,143 @@
+---
+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)
+- [Brakeman](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman)
+- [ESLint (Javascript)](https://gitlab.com/gitlab-org/security-products/analyzers/eslint)
+- [SpotBugs with the Find Sec Bugs plugin (Ant, Gradle and wrapper, Grails, Maven and wrapper, SBT)](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs)
+- [Flawfinder](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder)
+- [Gosec](https://gitlab.com/gitlab-org/security-products/analyzers/gosec)
+- [NodeJsScan](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan)
+- [PHP CS security-audit](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit)
+- [Secrets (Gitleaks, TruffleHog & Diffence secret detectors)](https://gitlab.com/gitlab-org/security-products/analyzers/secrets)
+- [Security Code Scan (.NET)](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan)
+- [TSLint (Typescript)](https://gitlab.com/gitlab-org/security-products/analyzers/tslint)
+- [Sobelow (Elixir Phoenix)](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow)
+
+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 | 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 84b45cbe6e6..7df86eedd18 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -13,7 +13,7 @@ to learn how to protect your organization.
If you are using [GitLab CI/CD](../../../ci/README.md), you can analyze your source code for known
vulnerabilities using Static Application Security Testing (SAST).
-You can take advantage of SAST by either [including the CI job](#configuring-sast) in
+You can take advantage of SAST by either [including the CI job](#configuration) in
your existing `.gitlab-ci.yml` file or by implicitly using
[Auto SAST](../../../topics/autodevops/index.md#auto-sast-ultimate)
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
@@ -73,30 +73,16 @@ The Java analyzers can also be used for variants like the
[Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html),
[Grails](https://grails.org/) and the [Maven wrapper](https://github.com/takari/maven-wrapper).
-## Configuring SAST
+## Configuration
-To enable SAST in your project, define a job in your `.gitlab-ci.yml` file that generates the
-[SAST report artifact](../../../ci/yaml/README.md#artifactsreportssast-ultimate).
+For GitLab 11.9 and later, to enable SAST, you must
+[include](../../../ci/yaml/README.md#includetemplate) the
+[`SAST.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml)
+that's provided as a part of your GitLab installation.
+For GitLab versions earlier than 11.9, you can copy and use the job as defined
+that template.
-This can be done in two ways:
-
-- For GitLab 11.9 and later, including the provided `SAST.gitlab-ci.yml` template (recommended).
-- Manually specifying the job definition. Not recommended unless using GitLab
- 11.8 and earlier.
-
-### Including the provided template
-
-NOTE: **Note:**
-The CI/CD SAST template is supported on GitLab 11.9 and later versions.
-For earlier versions, use the [manual job definition](#manual-job-definition-for-gitlab-115-and-later).
-
-A CI/CD [SAST template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml)
-with the default SAST job definition is provided as a part of your GitLab
-installation which you can [include](../../../ci/yaml/README.md#includetemplate)
-in your `.gitlab-ci.yml` file.
-
-To enable SAST using the provided template, add the following to your `.gitlab-ci.yml`
-file:
+Add the following to your `.gitlab-ci.yml` file:
```yaml
include:
@@ -106,14 +92,14 @@ include:
The included template will create a `sast` job in your CI/CD pipeline and scan
your project's source code for possible vulnerabilities.
-The report will be saved as a
+The results will be saved as a
[SAST report artifact](../../../ci/yaml/README.md#artifactsreportssast-ultimate)
that you can later download and analyze. Due to implementation limitations, we
always take the latest SAST artifact available. Behind the scenes, the
[GitLab SAST Docker image](https://gitlab.com/gitlab-org/security-products/sast)
is used to detect the languages/frameworks and in turn runs the matching scan tools.
-#### Customizing the SAST settings
+### Customizing the SAST settings
The SAST settings can be changed through environment variables by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
@@ -134,7 +120,7 @@ variables:
Because the template is [evaluated before](../../../ci/yaml/README.md#include)
the pipeline configuration, the last mention of the variable will take precedence.
-#### Overriding the SAST template
+### Overriding the SAST template
If you want to override the job definition (for example, change properties like
`variables` or `dependencies`), you need to declare a `sast` job after the
@@ -149,77 +135,57 @@ sast:
CI_DEBUG_TRACE: "true"
```
-### Manual job definition for GitLab 11.5 and later
+### Available variables
-For GitLab 11.5 and GitLab Runner 11.5 and later, the following `sast`
-job can be added:
+SAST can be [configured](#customizing-the-sast-settings) using environment variables.
-```yaml
-sast:
- stage: test
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - export SAST_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
- - |
- docker run \
- --env SAST_ANALYZER_IMAGES \
- --env SAST_ANALYZER_IMAGE_PREFIX \
- --env SAST_ANALYZER_IMAGE_TAG \
- --env SAST_DEFAULT_ANALYZERS \
- --env SAST_EXCLUDED_PATHS \
- --env SAST_BANDIT_EXCLUDED_PATHS \
- --env SAST_BRAKEMAN_LEVEL \
- --env SAST_GOSEC_LEVEL \
- --env SAST_FLAWFINDER_LEVEL \
- --env SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
- --env SAST_PULL_ANALYZER_IMAGE_TIMEOUT \
- --env SAST_RUN_ANALYZER_TIMEOUT \
- --volume "$PWD:/code" \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
- dependencies: []
- artifacts:
- reports:
- sast: gl-sast-report.json
-```
+#### Docker images
-You can supply many other [settings variables](https://gitlab.com/gitlab-org/security-products/sast#settings)
-via `docker run --env` to customize your job execution.
+The following are Docker image-related variables.
-### Manual job definition for GitLab 11.4 and earlier (deprecated)
+| 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). |
-CAUTION: **Deprecated:**
-Before GitLab 11.5, the SAST job and artifact had to be named specifically
-to automatically extract report data and show it in the merge request widget.
-While these old job definitions are still maintained, they have been deprecated
-and may be removed in the next major release, GitLab 12.0. You are strongly
-advised to update your current `.gitlab-ci.yml` configuration to reflect that change.
+### Vulnerability filters
-For GitLab 11.4 and earlier, the SAST job should look like:
+Some analyzers make it possible to filter out vulnerabilities under a given threshold.
-```yaml
-sast:
- image: docker:stable
- variables:
- DOCKER_DRIVER: overlay2
- allow_failure: true
- services:
- - docker:stable-dind
- script:
- - export SAST_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
- - docker run
- --env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}"
- --volume "$PWD:/code"
- --volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/sast:$SAST_VERSION" /app/bin/run /code
- artifacts:
- paths: [gl-sast-report.json]
-```
+| `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, 1=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
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index 3b01fe66e03..2a2385c00ae 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -26,8 +26,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
diff --git a/doc/user/asciidoc.md b/doc/user/asciidoc.md
index 06cc64b209d..df86b2a1cbe 100644
--- a/doc/user/asciidoc.md
+++ b/doc/user/asciidoc.md
@@ -170,6 +170,7 @@ Attach a block or paragraph to a list item using a list continuation (which you
* [x] checked
* [ ] not checked
```
+
#### Callout
```asciidoc
@@ -188,6 +189,7 @@ first term:: description of first term
second term::
description of second term
```
+
### Document Structure
#### Header
@@ -197,6 +199,7 @@ description of second term
Author Name <author@example.org>
v1.0, 2019-01-01
```
+
#### Sections
```asciidoc
@@ -217,6 +220,7 @@ include::basics.adoc[]
// define -a allow-uri-read to allow content to be read from URI
include::https://example.org/installation.adoc[]
```
+
### Blocks
```asciidoc
@@ -273,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]
@@ -369,4 +373,3 @@ video::300817511[vimeo]
// page break
<<<
```
-
diff --git a/doc/user/award_emojis.md b/doc/user/award_emojis.md
index e4fd08a582c..1b1d126b726 100644
--- a/doc/user/award_emojis.md
+++ b/doc/user/award_emojis.md
@@ -7,7 +7,7 @@
When you're collaborating online, you get fewer opportunities for high-fives
and thumbs-ups. Emoji can be awarded to [issues](project/issues/index.md), [merge requests](project/merge_requests/index.md),
-[snippets](snippets.md), and anywhere you can have a discussion.
+[snippets](snippets.md), and anywhere you can have a thread.
![Award emoji](img/award_emoji_select.png)
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index 2246ea8ed5a..d5c836554ce 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -19,8 +19,8 @@ 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, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, navigate to your group's **Kubernetes** page.
Install Helm first as it's used to install other applications.
@@ -232,8 +232,8 @@ 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, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, 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,6 +251,7 @@ The applications below can be uninstalled.
| Application | GitLab version | Notes |
| ----------- | -------------- | ----- |
+| GitLab Runner | 12.2+ | Any running pipelines will be canceled. |
| 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. |
| Prometheus | 11.11+ | All data will be deleted and cannot be restored. |
@@ -258,8 +259,8 @@ The applications below can be uninstalled.
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, navigate to your project's **Operations > Kubernetes**.
+ - Group-level cluster, navigate to your group's **Kubernetes** page.
1. Select your cluster.
1. Click the **Uninstall** button for the application.
diff --git a/doc/user/discussions/img/automatically_resolve_outdated_discussions.png b/doc/user/discussions/img/automatically_resolve_outdated_discussions.png
index ba129e7a618..d31216a7e2e 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_discussions.png b/doc/user/discussions/img/btn_new_issue_for_all_discussions.png
deleted file mode 100644
index 3306bf2e60e..00000000000
--- a/doc/user/discussions/img/btn_new_issue_for_all_discussions.png
+++ /dev/null
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
new file mode 100644
index 00000000000..f24c84a2348
--- /dev/null
+++ 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 b363e0035e8..7b87d6e44d7 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 2b06cdcc055..4798ff4b658 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 206ddebf54b..685c30e5004 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/discussion_view.png b/doc/user/discussions/img/discussion_view.png
deleted file mode 100644
index 3a2b766ed7e..00000000000
--- a/doc/user/discussions/img/discussion_view.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/discussions/img/image_resolved_discussion.png b/doc/user/discussions/img/image_resolved_discussion.png
index ed00b5c77fe..9feded27c92 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/merge_request_commits_tab.png b/doc/user/discussions/img/merge_request_commits_tab.png
index 41a3648f390..065d4be61f0 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 34176f3fa8e..fc6299961a5 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 e4adb5f2c2d..1794b682911 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 920ea05ad66..204cc840d9e 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 1fb54348547..aa15ca7fb98 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 38b44bda0d2..0f52bee7d89 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 da895ceb89f..3441efc1572 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/new_issue_for_thread.png b/doc/user/discussions/img/new_issue_for_thread.png
new file mode 100644
index 00000000000..2264da0b5b5
--- /dev/null
+++ 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 91c3b396844..9bb4428184e 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
new file mode 100644
index 00000000000..9314e3a6490
--- /dev/null
+++ 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 916ef5b7452..812e4ac966a 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
new file mode 100644
index 00000000000..1517902c61c
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..8359ab3143c
--- /dev/null
+++ b/doc/user/discussions/img/preview_issue_for_threads.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 7c19fac31a2..0319ec999fd 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
new file mode 100644
index 00000000000..873c302f570
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..c2a8fdebee7
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..be2a4365297
--- /dev/null
+++ 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 bd9880c329a..df5c4dd0fcc 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 4bf53a81b9c..1b91506b477 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/swipe_view.png b/doc/user/discussions/img/swipe_view.png
index 82d6e52173c..287d52a0811 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
new file mode 100644
index 00000000000..8c1fd9d5acf
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..6ac815cf874
--- /dev/null
+++ 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 d9e90708e87..062a96723dd 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 f2156720af7..3cb765c0463 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -1,4 +1,4 @@
-# Discussions
+# Threads
The ability to contribute conversationally is offered throughout GitLab.
@@ -12,7 +12,7 @@ You can leave a comment in the following places:
- commit diffs
There are standard comments, and you also have the option to create a comment
-in the form of a threaded discussion. A comment can also be [turned into a discussion](#start-a-discussion-by-replying-to-a-standard-comment)
+in the form of a thread. A comment can also be [turned into a thread](#start-a-thread-by-replying-to-a-standard-comment)
when it receives a reply.
The comment area supports [Markdown] and [quick actions]. You can edit your own
@@ -21,41 +21,39 @@ 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 discussion comment creates a reply in the
-discussion thread. Email replies support [Markdown] and [quick actions], just as if you replied from the web.
+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.
-## Resolvable comments and discussions
+## Resolvable comments and threads
> **Notes:**
>
> - The main feature was [introduced][ce-5022] in GitLab 8.11.
-> - Resolvable discussions can be added only to merge request diffs.
+> - Resolvable threads can be added only to merge request diffs.
-Discussion resolution helps keep track of progress during planning or code review.
+Thread resolution helps keep track of progress during planning or code review.
-Every standard comment or discussion thread in merge requests, commits, commit diffs, and
+Every standard comment or thread in merge requests, commits, commit diffs, and
snippets is initially displayed as unresolved. They can then be individually resolved by anyone
with at least Developer access to the project or by the author of the change being reviewed.
-The need to resolve all standard comments or discussions prevents you from forgetting
-to address feedback and lets you hide discussions that are no longer relevant.
+The need to resolve all standard comments or threads prevents you from forgetting
+to address feedback and lets you hide threads that are no longer relevant.
-!["A discussion between two people on a piece of code"][discussion-view]
+!["A thread between two people on a piece of code"](img/thread_view.png)
-### Commit discussions in the context of a merge request
+### Commit threads in the context of a merge request
> [Introduced][ce-31847] in GitLab 10.3.
-For reviewers with commit-based workflow, it may be useful to add discussions to
-specific commit diffs in the context of a merge request. These discussions will
+For reviewers with commit-based workflow, it may be useful to add threads to
+specific commit diffs in the context of a merge request. These threads will
persist through a commit ID change when:
- force-pushing after a rebase
- amending a commit
-This functionality is also demonstrated in the video [How to use Merge Request Commit Discussions](https://www.youtube.com/watch?v=TviJH6oRboo).
-
-To create a commit diff discussion:
+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.
@@ -67,141 +65,141 @@ To create a commit diff discussion:
![Commit diff discussion in merge request context](img/commit_comment_mr_context.png)
-1. Any discussions created this way will be shown in the merge request's
+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)
-Discussions created this way will only appear in the original merge request
+Threads created this way will only appear in the original merge request
and not when navigating to that commit under your project's
**Repository > Commits** page.
TIP: **Tip:**
-When a link of a commit reference is found in a discussion inside a merge
+When a link of a commit reference is found in a thread inside a merge
request, it will be automatically converted to a link in the context of the
current merge request.
-### Jumping between unresolved discussions
+### Jumping between unresolved threads
When a merge request has a large number of comments it can be difficult to track
-what remains unresolved. You can jump between unresolved discussions with the
-Jump button next to the Reply field on a discussion.
+what remains unresolved. You can jump between unresolved threads with the
+Jump button next to the Reply field on a thread.
-You can also jump to the first unresolved discussion from the button next to the
-resolved discussions tracker.
+You can also jump to the first unresolved thread from the button next to the
+resolved threads tracker.
-!["3/4 discussions resolved"][discussions-resolved]
+!["8/9 threads resolved"](img/threads_resolved.png)
-### Marking a comment or discussion as resolved
+### Marking a comment or thread as resolved
-You can mark a discussion as resolved by clicking the **Resolve discussion**
-button at the bottom of the discussion.
+You can mark a thread as resolved by clicking the **Resolve thread**
+button at the bottom of the thread.
-!["Resolve discussion" button][resolve-discussion-button]
+!["Resolve thread" button](img/resolve_thread_button.png)
Alternatively, you can mark each comment as resolved individually.
-!["Resolve comment" button][resolve-comment-button]
+!["Resolve comment" button](img/resolve_comment_button.png)
-### Move all unresolved discussions in a merge request to an issue
+### Move all unresolved threads in a merge request to an issue
> [Introduced][ce-8266] in GitLab 9.1
-To continue all open discussions from a merge request in a new issue, click the
-**Resolve all discussions in new issue** button.
+To continue all open threads from a merge request in a new issue, click the
+**Resolve all threads in new issue** button.
-![Open new issue for all unresolved discussions](img/btn_new_issue_for_all_discussions.png)
+![Open new issue for all unresolved threads](img/btn_new_issue_for_all_threads.png)
-Alternatively, when your project only accepts merge requests [when all discussions
-are resolved](#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved),
+Alternatively, when your project only accepts merge requests [when all threads
+are resolved](#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved),
there will be an **open an issue to resolve them later** link in the merge
request widget.
-![Link in merge request widget](img/resolve_discussion_open_issue.png)
+![Link in merge request widget](img/resolve_thread_open_issue.png)
This will prepare an issue with its content referring to the merge request and
-the unresolved discussions.
+the unresolved threads.
-![Issue mentioning discussions in a merge request](img/preview_issue_for_discussions.png)
+![Issue mentioning threads in a merge request](img/preview_issue_for_threads.png)
-Hitting **Submit issue** will cause all discussions to be marked as resolved and
+Hitting **Submit issue** will cause all threads to be marked as resolved and
add a note referring to the newly created issue.
-![Mark discussions as resolved notice](img/resolve_discussion_issue_notice.png)
+![Mark threads as resolved notice](img/resolve_thread_issue_notice.png)
You can now proceed to merge the merge request from the UI.
-### Moving a single discussion to a new issue
+### Moving a single thread to a new issue
> [Introduced][ce-8266] in GitLab 9.1
-To create a new issue for a single discussion, you can use the **Resolve this
-discussion in a new issue** button.
+To create a new issue for a single thread, you can use the **Resolve this
+thread in a new issue** button.
-![Create issue for discussion](img/new_issue_for_discussion.png)
+![Create issue for thread](img/new_issue_for_thread.png)
This will direct you to a new issue prefilled with the content of the
-discussion, similar to the issues created for delegating multiple
-discussions at once. Saving the issue will mark the discussion as resolved and
-add a note to the merge request discussion referencing the new issue.
+thread, similar to the issues created for delegating multiple
+threads at once. Saving the issue will mark the thread as resolved and
+add a note to the merge request thread referencing the new issue.
-![New issue for a single discussion](img/preview_issue_for_discussion.png)
+![New issue for a single thread](img/preview_issue_for_thread.png)
-### Only allow merge requests to be merged if all discussions are resolved
+### Only allow merge requests to be merged if all threads are resolved
> [Introduced][ce-7125] in GitLab 8.14.
-You can prevent merge requests from being merged until all discussions are
+You can prevent merge requests from being merged until all threads are
resolved.
Navigate to your project's settings page, select the
-**Only allow merge requests to be merged if all discussions are resolved** check
+**Only allow merge requests to be merged if all threads are resolved** check
box and hit **Save** for the changes to take effect.
-![Only allow merge if all the discussions are resolved settings](img/only_allow_merge_if_all_discussions_are_resolved.png)
+![Only allow merge if all the threads are resolved settings](img/only_allow_merge_if_all_threads_are_resolved.png)
-From now on, you will not be able to merge from the UI until all discussions
+From now on, you will not be able to merge from the UI until all threads
are resolved.
-![Only allow merge if all the discussions are resolved message](img/only_allow_merge_if_all_discussions_are_resolved_msg.png)
+![Only allow merge if all the threads are resolved message](img/resolve_thread_open_issue.png)
-### Automatically resolve merge request diff discussions when they become outdated
+### Automatically resolve merge request diff threads when they become outdated
> [Introduced][ce-14053] in GitLab 10.0.
-You can automatically resolve merge request diff discussions on lines modified
+You can automatically resolve merge request diff threads on lines modified
with a new push.
Navigate to your project's settings page, select the **Automatically resolve
-merge request diffs discussions on lines changed with a push** check box and hit
+merge request diffs threads on lines changed with a push** check box and hit
**Save** for the changes to take effect.
-![Automatically resolve merge request diff discussions when they become outdated](img/automatically_resolve_outdated_discussions.png)
+![Automatically resolve merge request diff threads when they become outdated](img/automatically_resolve_outdated_discussions.png)
-From now on, any discussions on a diff will be resolved by default if a push
-makes that diff section outdated. Discussions on lines that don't change and
-top-level resolvable discussions are not automatically resolved.
+From now on, any threads on a diff will be resolved by default if a push
+makes that diff section outdated. Threads on lines that don't change and
+top-level resolvable threads are not automatically resolved.
-## Commit discussions
+## Commit threads
-You can add comments and discussion threads to a particular commit under your
+You can add comments and threads to a particular commit under your
project's **Repository > Commits**.
CAUTION: **Attention:**
-Discussions created this way will be lost if the commit ID changes after a
+Threads created this way will be lost if the commit ID changes after a
force push.
## Threaded discussions
> [Introduced][ce-7527] in GitLab 9.1.
-While resolvable discussions are only available to merge request diffs,
-discussions can also be added without a diff. You can start a specific
-discussion which will look like a thread, on issues, commits, snippets, and
+While resolvable threads are only available to merge request diffs,
+threads can also be added without a diff. You can start a specific
+thread which will look like a thread, on issues, commits, snippets, and
merge requests.
To start a threaded discussion, click on the **Comment** button toggle dropdown,
-select **Start discussion** and click **Start discussion** when you're ready to
+select **Start thread** and click **Start thread** when you're ready to
post the comment.
![Comment type toggle](img/comment_type_toggle.gif)
@@ -209,56 +207,57 @@ post the comment.
This will post a comment with a single thread to allow you to discuss specific
comments in greater detail.
-![Discussion comment](img/discussion_comment.png)
+![Thread comment](img/discussion_comment.png)
-## Image discussions
+## Image threads
> [Introduced][ce-14061] in GitLab 10.1.
-Sometimes a discussion is revolved around an image. With image discussions,
-you can easily target a specific coordinate of an image and start a discussion
-around it. Image discussions are available in merge requests and commit detail views.
+Sometimes a thread is revolved around an image. With image threads,
+you can easily target a specific coordinate of an image and start a thread
+around it. Image threads are available in merge requests and commit detail views.
-To start an image discussion, hover your mouse over the image. Your mouse pointer
+To start an image thread, hover your mouse over the image. Your mouse pointer
should convert into an icon, indicating that the image is available for commenting.
-Simply click anywhere on the image to create a new discussion.
+Simply click anywhere on the image to create a new thread.
-![Start image discussion](img/start_image_discussion.gif)
+![Start image thread](img/start_image_discussion.gif)
After you click on the image, a comment form will be displayed that would be the start
-of your discussion. Once you save your comment, you will see a new badge displayed on
-top of your image. This badge represents your discussion.
+of your thread. Once you save your comment, you will see a new badge displayed on
+top of your image. This badge represents your thread.
>**Note:**
-This discussion badge is typically associated with a number that is only used as a visual
-reference for each discussion. In the merge request discussion tab,
-this badge will be indicated with a comment icon since each discussion will render a new
+This thread badge is typically associated with a number that is only used as a visual
+reference for each thread. In the merge request thread tab,
+this badge will be indicated with a comment icon since each thread will render a new
image section.
-Image discussions also work on diffs that replace an existing image. In this diff view
-mode, you can toggle the different view modes and still see the discussion point badges.
+Image threads also work on diffs that replace an existing image. In this diff view
+mode, you can toggle the different view modes and still see the thread point badges.
| 2-up | Swipe | Onion Skin |
| :-----------: | :----------: | :----------: |
| ![2-up view](img/two_up_view.png) | ![swipe view](img/swipe_view.png) | ![onion skin view](img/onion_skin_view.png) |
-Image discussions also work well with resolvable discussions. Resolved discussions
+Image threads also work well with resolvable threads. Resolved threads
on diffs (not on the merge request discussion tab) will appear collapsed on page
load and will have a corresponding badge counter to match the counter on the image.
-![Image resolved discussion](img/image_resolved_discussion.png)
+![Image resolved thread](img/image_resolved_discussion.png)
## Lock discussions
> [Introduced][ce-14531] in GitLab 10.1.
-For large projects with many contributors, it may be useful to stop discussions
+For large projects with many contributors, it may be useful to stop threads
in issues or merge requests in these scenarios:
-- The project maintainer has already resolved the discussion and it is not helpful
- for continued feedback. The project maintainer has already directed new conversation
+- The project maintainer has already resolved the thread and it is not helpful
+ for continued feedback.
+- The project maintainer has already directed new conversation
to newer issues or merge requests.
-- The people participating in the discussion are trolling, abusive, or otherwise
+- The people participating in the thread are trolling, abusive, or otherwise
being unproductive.
In these cases, a user with Developer permissions or higher in the project can lock (and unlock)
@@ -300,7 +299,7 @@ in an MR and click on the **Start a review** button.
Once a review is started, you will see any comments that are part of this review marked `Pending`.
All comments that are part of a review show two buttons:
-- **Submit review**: Submits all comments that are part of the review, making them visible to other users.
+- **Finish review**: Submits all comments that are part of the review, making them visible to other users.
- **Add comment now**: Submits the specific comment as a regular comment instead of as part of the review.
![A comment that is part of a review](img/pending_review_comment.png)
@@ -317,20 +316,20 @@ This will add the comment to the review.
![Second review comment](img/mr_review_second_comment_added.png)
-### Resolving/Unresolving discussions
+### Resolving/Unresolving threads
-Review comments can also resolve/unresolve [resolvable discussions](#resolvable-comments-and-discussions).
+Review comments can also resolve/unresolve [resolvable threads](#resolvable-comments-and-threads).
When replying to a comment, you will see a checkbox that you can click in order to resolve or unresolve
-the discussion once published.
+the thread once published.
![Resolve checkbox](img/mr_review_resolve.png)
-![Unresolve checkbox](img/mr_review_unresolve.png)
-If a particular pending comment will resolve or unresolve the discussion, this will be shown on the pending
+If a particular pending comment will resolve or unresolve the thread, this will be shown on the pending
comment itself.
![Resolve status](img/mr_review_resolve2.png)
-![Unresolve status](img/mr_review_unresolve2.png)
+
+![Unresolve status](img/mr_review_unresolve.png)
### Submitting a review
@@ -356,7 +355,7 @@ Replying to this email will, consequentially, create a new comment on the associ
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26723) in GitLab 11.5.
For issues with many comments like activity notes and user comments, sometimes
-finding useful information can be hard. There is a way to filter comments from single notes and discussions for merge requests and issues.
+finding useful information can be hard. There is a way to filter comments from single notes and threads for merge requests and issues.
From a merge request's **Discussion** tab, or from an epic/issue overview, find the filter's dropdown menu on the right side of the page, from which you can choose one of the following options:
@@ -376,7 +375,7 @@ from any device you're logged into.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6.
As a reviewer, you're able to suggest code changes with a simple
-markdown syntax in Merge Request Diff discussions. Then, the
+markdown syntax in Merge Request Diff threads. Then, the
Merge Request author (or other users with appropriate
[permission](../permissions.md)) is able to apply these
suggestions with a click, which will generate a commit in
@@ -399,7 +398,7 @@ the Merge Request authored by the user that applied them.
![Apply suggestions](img/suggestion.png)
Once the author applies a suggestion, it will be marked with the **Applied** label,
-the discussion will be automatically resolved, and GitLab will create a new commit
+the thread will be automatically resolved, and GitLab will create a new commit
with the message `Apply suggestion to <file-name>` and push the suggested change
directly into the codebase in the merge request's branch.
[Developer permission](../permissions.md) is required to do so.
@@ -413,8 +412,8 @@ Custom commit messages will be introduced by
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310) in GitLab 11.10.
Reviewers can also suggest changes to multiple lines with a single suggestion
-within Merge Request diff discussions by adjusting the range offsets. The
-offsets are relative to the position of the diff discussion, and specify the
+within Merge Request diff threads by adjusting the range offsets. The
+offsets are relative to the position of the diff thread, and specify the
range to be replaced by the suggestion when it is applied.
![Multi-line suggestion syntax](img/multi-line-suggestion-syntax.png)
@@ -430,25 +429,26 @@ Suggestions covering multiple lines are limited to 100 lines _above_ and 100
lines _below_ the commented diff line, allowing up to 200 changed lines per
suggestion.
-## Start a discussion by replying to a standard comment
+## Start a thread by replying to a standard comment
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30299) in GitLab 11.9
-To reply to a standard (non-discussion) comment, you can use the **Reply to comment** button.
+To reply to a standard (non-thread) comment, you can use the **Reply to comment** button.
![Reply to comment button](img/reply_to_comment_button.png)
-The **Reply to comment** button is only displayed if you have permissions to reply to an existing discussion, or start a discussion from a standard comment.
+The **Reply to comment** button is only displayed if you have permissions to reply to an existing thread, or start a thread from a standard comment.
Clicking on the **Reply to comment** button will bring the reply area into focus and you can type your reply.
![Reply to comment feature](img/reply_to_comment.gif)
-Relying to a non-discussion comment will convert the non-discussion comment to a
-threaded discussion once the reply is submitted. This conversion is considered an edit
+Replying to a non-thread comment will convert the non-thread comment to a
+thread once the reply is submitted. This conversion is considered an edit
to the original comment, so a note about when it was last edited will appear underneath it.
-This feature only exists for Issues, Merge requests, and Epics. Commits, Snippets and Merge request diff discussions are not supported yet.
+This feature only exists for Issues, Merge requests, and Epics. Commits, Snippets and Merge request diff threads are
+not supported yet.
[ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022
[ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125
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..d08503dc312
--- /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..5b5f75c2dd9
--- /dev/null
+++ b/doc/user/group/bulk_editing/index.md
@@ -0,0 +1,26 @@
+# Bulk editing issue and merge request milestones **(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 [GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
+
+> NOTE: **Note:**
+>
+> - 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.
+
+Milestones can be updated simultaneously across multiple issues or merge requests by using the bulk editing feature.
+
+![Bulk editing](img/bulk-editing.png)
+
+To bulk update group issue or merge request milestones:
+
+1. Navigate to the issues or merge requests list.
+1. Click the **Edit issues** or **Edit merge requests** button.
+ - This will open a sidebar on the right-hand side of your screen where an editable field
+ for milestones will be displayed.
+ - Checkboxes will also appear beside each issue or merge request.
+1. Check the checkbox beside each issue to be edited.
+1. Select the desired milestone 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..625c5440ec0 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
@@ -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/custom_project_templates.md b/doc/user/group/custom_project_templates.md
index 8a85c375490..7cdba8cf2b5 100644
--- a/doc/user/group/custom_project_templates.md
+++ b/doc/user/group/custom_project_templates.md
@@ -24,7 +24,7 @@ project in the group will be available to every logged in user.
However, private projects will be available only if the user is a member of the project.
NOTE: **Note:**
-Projects of nested subgroups of a selected template source cannot be used.
+Only direct subgroups can be set as the template source. Projects of nested subgroups of a selected template source cannot be used.
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).
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index 601ffd4947b..4ab562b655f 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -205,12 +205,12 @@ You may also consult the [group permissions table][permissions].
These text fields also fully support
[GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
-## Comment, or start a discussion
+## Comment, or start a thread
Once you wrote your comment, you can either:
- Click "Comment" and your comment will be published.
-- Click "Start discussion": start a thread within that epic's thread to discuss specific points.
+- Click "Start thread": start a thread within that epic's discussion to discuss specific points.
## Award emoji
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index db348c678eb..14c831fe671 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
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..8c5dbe36f98 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/index.md b/doc/user/group/saml_sso/index.md
index 54923ab69ff..e3f657af564 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -71,7 +71,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 2d408766db8..55c5a18db7d 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -27,23 +27,23 @@ The following identity providers are supported:
- [Group SSO](index.md) needs to be configured.
- The `scim_group` feature flag must be enabled:
- Run the following commands in a Rails console:
+ Run the following commands in a Rails console:
- ```sh
- # Omnibus GitLab
- gitlab-rails console
+ ```sh
+ # Omnibus GitLab
+ gitlab-rails console
- # Installation from source
- cd /home/git/gitlab
- sudo -u git -H bin/rails console RAILS_ENV=production
- ```
+ # Installation from source
+ cd /home/git/gitlab
+ sudo -u git -H bin/rails console RAILS_ENV=production
+ ```
- To enable SCIM for a group named `group_name`:
+ To enable SCIM for a group named `group_name`:
- ```ruby
- group = Group.find_by_full_path('group_name')
- Feature.enable(:group_scim, group)
- ```
+ ```ruby
+ group = Group.find_by_full_path('group_name')
+ Feature.enable(:group_scim, group)
+ ```
### GitLab configuration
@@ -85,26 +85,26 @@ You can then test the connection clicking on `Test Connection`.
1. Map the `userPricipalName` to `emails[type eq "work"].value` and `mailNickname` to
`userName`.
- Example configuration:
+ Example configuration:
- ![Azure's attribute mapping configuration](img/scim_attribute_mapping.png)
+ ![Azure's attribute mapping configuration](img/scim_attribute_mapping.png)
1. Click on **Show advanced options > Edit attribute list for AppName**.
1. Leave the `id` as the primary and only required field.
- NOTE: **Note:**
- `username` should neither be primary nor required as we don't support
- that field on GitLab SCIM yet.
+ NOTE: **Note:**
+ `username` should neither be primary nor required as we don't support
+ that field on GitLab SCIM yet.
- ![Azure's attribute advanced configuration](img/scim_advanced.png)
+ ![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`.
- 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.
+ 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.
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.
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 501d74c76d1..db2759727a5 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -124,12 +124,12 @@ merge requests, code snippets, and commits.
When performing inline reviews to implementations
to your codebase through merge requests you can
-gather feedback through [resolvable discussions](discussions/index.md#resolvable-comments-and-discussions).
+gather feedback through [resolvable threads](discussions/index.md#resolvable-comments-and-threads).
### GitLab Flavored Markdown (GFM)
Read through the [GFM documentation](markdown.md) to learn how to apply
-the best of GitLab Flavored Markdown in your discussions, comments,
+the best of GitLab Flavored Markdown in your threads, comments,
issues and merge requests descriptions, and everywhere else GMF is
supported.
@@ -148,7 +148,7 @@ requests you're assigned to.
[Snippets](snippets.md) are code blocks that you want to store in GitLab, from which
you have quick access to. You can also gather feedback on them through
-[discussions](#discussions).
+[Discussions](#Discussions).
## Integrations
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 83629fc2e4b..37f3f21f539 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -40,7 +40,7 @@ repositories are also processed with CommonMark. As of 11.8, the [Redcarpet Ruby
has been removed and all issues and comments, including those from pre-11.1, are now processed
using the [CommonMark Ruby Library](https://github.com/gjtorikian/commonmarker).
-The documentation website had its [markdown engine migrated from Redcarpet to Kramdown](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/108)
+The documentation website had its [markdown engine migrated from Redcarpet to Kramdown](https://gitlab.com/gitlab-org/gitlab-docs/merge_requests/108)
in October 2018.
You may have older issues, merge requests, or Markdown documents in your
@@ -556,7 +556,7 @@ Inline `code` has `back-ticks around` it.
Similarly, a whole block of code can be fenced with triple backticks ```` ``` ````,
triple tildes (`~~~`), or indended 4 or more spaces to achieve a similar effect for
-a larger body of code. test.
+a larger body of code.
~~~
```
@@ -586,9 +586,11 @@ def function():
print s
```
- Using 4 spaces
- is like using
- 3-backtick fences.
+```
+Using 4 spaces
+is like using
+3-backtick fences.
+```
~~~
Tildes are OK too.
@@ -1158,13 +1160,15 @@ Examples:
+ 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
- Or minuses
@@ -1182,14 +1186,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
---
@@ -1203,14 +1207,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/permissions.md b/doc/user/permissions.md
index 1b279173d1c..044be05b932 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -60,7 +60,7 @@ The following table depicts the various user permission levels in a project.
| View confidential issues | (*2*) | ✓ | ✓ | ✓ | ✓ |
| Assign issues | | ✓ | ✓ | ✓ | ✓ |
| Label issues | | ✓ | ✓ | ✓ | ✓ |
-| Lock issue discussions | | ✓ | ✓ | ✓ | ✓ |
+| Lock issue threads | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage related issues **(STARTER)** | | ✓ | ✓ | ✓ | ✓ |
| Create issue from vulnerability **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
@@ -81,7 +81,7 @@ The following table depicts the various user permission levels in a project.
| Create new merge request | | | ✓ | ✓ | ✓ |
| Assign merge requests | | | ✓ | ✓ | ✓ |
| Label merge requests | | | ✓ | ✓ | ✓ |
-| Lock merge request discussions | | | ✓ | ✓ | ✓ |
+| Lock merge request threads | | | ✓ | ✓ | ✓ |
| Manage/Accept merge requests | | | ✓ | ✓ | ✓ |
| Create new environments | | | ✓ | ✓ | ✓ |
| Stop environments | | | ✓ | ✓ | ✓ |
@@ -209,13 +209,16 @@ group.
| Create project in group | | | ✓ | ✓ | ✓ |
| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
+| Create subgroup | | | | ✓ (1) | ✓ |
| Edit group | | | | | ✓ |
-| Create subgroup | | | | | ✓ |
| Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ |
| Delete group epic **(ULTIMATE)** | | | | | ✓ |
| View group Audit Events | | | | | ✓ |
+- (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
When you add a member to a subgroup, they inherit the membership and
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index e3e8c9a0d6d..d3a634c7b9d 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -183,29 +183,29 @@ a new set of recovery codes with SSH:
1. You will then be prompted to confirm that you want to generate new codes.
Continuing this process invalidates previously saved codes:
- ```sh
- Are you sure you want to generate new two-factor recovery codes?
- Any existing recovery codes you saved will be invalidated. (yes/no)
-
- yes
-
- Your two-factor authentication recovery codes are:
-
- 119135e5a3ebce8e
- 11f6v2a498810dcd
- 3924c7ab2089c902
- e79a3398bfe4f224
- 34bd7b74adbc8861
- f061691d5107df1a
- 169bf32a18e63e7f
- b510e7422e81c947
- 20dbed24c5e74663
- df9d3b9403b9c9f0
-
- During sign in, use one of the codes above when prompted for your
- two-factor code. Then, visit your Profile Settings and add a new device
- so you do not lose access to your account again.
- ```
+ ```sh
+ Are you sure you want to generate new two-factor recovery codes?
+ Any existing recovery codes you saved will be invalidated. (yes/no)
+
+ yes
+
+ Your two-factor authentication recovery codes are:
+
+ 119135e5a3ebce8e
+ 11f6v2a498810dcd
+ 3924c7ab2089c902
+ e79a3398bfe4f224
+ 34bd7b74adbc8861
+ f061691d5107df1a
+ 169bf32a18e63e7f
+ b510e7422e81c947
+ 20dbed24c5e74663
+ df9d3b9403b9c9f0
+
+ During sign in, use one of the codes above when prompted for your
+ two-factor code. Then, visit your Profile Settings and add a new device
+ so you do not lose access to your account again.
+ ```
1. Go to the GitLab sign-in page and enter your username/email and password.
When prompted for a two-factor code, enter one of the recovery codes obtained
diff --git a/doc/user/project/autocomplete_characters.md b/doc/user/project/autocomplete_characters.md
new file mode 100644
index 00000000000..9ebf7f821a1
--- /dev/null
+++ b/doc/user/project/autocomplete_characters.md
@@ -0,0 +1,48 @@
+# Autocomplete characters
+
+The autocomplete characters provide a quick way of entering field values into
+Markdown fields. When you start typing a word in a Markdown field with one of
+the following characters, GitLab progressively autocompletes against a set of
+matching values. The string matching is not case sensitive.
+
+| Character | Autocompletes |
+| :-------- | :------------ |
+| `~` | Labels |
+| `%` | Milestones |
+| `@` | Users and groups |
+| `#` | Issues |
+| `!` | Merge requests |
+| `&` | Epics |
+| `$` | Snippets |
+| `:` | Emoji |
+| `/` | Quick Actions |
+
+Up to 5 of the most relevant matches are displayed in a popup list. When you
+select an item from the list, the value is entered in the field. The more
+characters you enter, the more precise the matches are.
+
+Autocomplete characters are useful when combined with [Quick Actions](quick_actions.md).
+
+## Example
+
+Assume your GitLab instance includes the following users:
+
+| Username | Name |
+| :-------------- | :--- |
+| alessandra | Rosy Grant |
+| lawrence.white | Kelsey Kerluke |
+| leanna | Rosemarie Rogahn |
+| logan_gutkowski | Lee Wuckert |
+| shelba | Josefine Haley |
+
+In an Issue comment, entering `@l` results in the following popup list
+appearing. Note that user `shelba` is not included, because the list includes
+only the 5 users most relevant to the Issue.
+
+![Popup list which includes users whose username or name contains the letter `l`](img/autocomplete_characters_example1_v12_0.png)
+
+If you continue to type, `@le`, the popup list changes to the following. The
+popup now only includes users where `le` appears in their username, or a word in
+their name.
+
+![Popup list which includes users whose username or name contains the string `le`](img/autocomplete_characters_example2_v12_0.png)
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 ea09f9f1547..55a9fbabf98 100644
--- a/doc/user/project/clusters/eks_and_gitlab/index.md
+++ b/doc/user/project/clusters/eks_and_gitlab/index.md
@@ -40,97 +40,97 @@ then click **Add an existing Kubernetes cluster**.
A few details from the EKS cluster will be required to connect it to GitLab:
-1. **Retrieve the certificate**: A valid Kubernetes certificate is needed to
- authenticate to the EKS cluster. We will use the certificate created by default.
- Open a shell and use `kubectl` to retrieve it:
+1. **Retrieve the certificate**: A valid Kubernetes certificate is needed to
+ authenticate to the EKS cluster. We will use the certificate created by default.
+ Open a shell and use `kubectl` to retrieve it:
- - List the secrets with `kubectl get secrets`, and one should named similar to
- `default-token-xxxxx`. Copy that token name for use below.
- - Get the certificate with:
+ - List the secrets with `kubectl get secrets`, and one should named similar to
+ `default-token-xxxxx`. Copy that token name for use below.
+ - Get the certificate with:
- ```sh
- kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
- ```
-
-1. **Create admin token**: A `cluster-admin` token is required to install and
- manage Helm Tiller. GitLab establishes mutual SSL auth with Helm Tiller
- and creates limited service accounts for each application. To create the
- token we will create an admin service account as follows:
-
- 2.1. Create a file called `eks-admin-service-account.yaml` with contents:
-
- ```yaml
- apiVersion: v1
- kind: ServiceAccount
- metadata:
- name: eks-admin
- namespace: kube-system
- ```
-
- 2.2. Apply the service account to your cluster:
-
- ```bash
- kubectl apply -f eks-admin-service-account.yaml
+ ```sh
+ kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
```
- Output:
-
- ```bash
- serviceaccount "eks-admin" created
- ```
-
- 2.3. Create a file called `eks-admin-cluster-role-binding.yaml` with contents:
-
- ```yaml
- apiVersion: rbac.authorization.k8s.io/v1beta1
- kind: ClusterRoleBinding
- metadata:
- name: eks-admin
- roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: cluster-admin
- subjects:
- - kind: ServiceAccount
- name: eks-admin
- namespace: kube-system
- ```
-
- 2.4. Apply the cluster role binding to your cluster:
-
- ```bash
- kubectl apply -f eks-admin-cluster-role-binding.yaml
- ```
-
- Output:
-
- ```bash
- clusterrolebinding "eks-admin" created
- ```
-
- 2.5. Retrieve the token for the `eks-admin` service account:
-
- ```bash
- kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep eks-admin | awk '{print $1}')
- ```
-
- Copy the `<authentication_token>` value from the output:
-
- ```yaml
- Name: eks-admin-token-b5zv4
- Namespace: kube-system
- Labels: <none>
- Annotations: kubernetes.io/service-account.name=eks-admin
- kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8
-
- Type: kubernetes.io/service-account-token
-
- Data
- ====
- ca.crt: 1025 bytes
- namespace: 11 bytes
- token: <authentication_token>
- ```
+1. **Create admin token**: A `cluster-admin` token is required to install and
+ manage Helm Tiller. GitLab establishes mutual SSL auth with Helm Tiller
+ and creates limited service accounts for each application. To create the
+ token we will create an admin service account as follows:
+
+ 2.1. Create a file called `eks-admin-service-account.yaml` with contents:
+
+ ```yaml
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: eks-admin
+ namespace: kube-system
+ ```
+
+ 2.2. Apply the service account to your cluster:
+
+ ```bash
+ kubectl apply -f eks-admin-service-account.yaml
+ ```
+
+ Output:
+
+ ```bash
+ serviceaccount "eks-admin" created
+ ```
+
+ 2.3. Create a file called `eks-admin-cluster-role-binding.yaml` with contents:
+
+ ```yaml
+ apiVersion: rbac.authorization.k8s.io/v1beta1
+ kind: ClusterRoleBinding
+ metadata:
+ name: eks-admin
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: cluster-admin
+ subjects:
+ - kind: ServiceAccount
+ name: eks-admin
+ namespace: kube-system
+ ```
+
+ 2.4. Apply the cluster role binding to your cluster:
+
+ ```bash
+ kubectl apply -f eks-admin-cluster-role-binding.yaml
+ ```
+
+ Output:
+
+ ```bash
+ clusterrolebinding "eks-admin" created
+ ```
+
+ 2.5. Retrieve the token for the `eks-admin` service account:
+
+ ```bash
+ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep eks-admin | awk '{print $1}')
+ ```
+
+ Copy the `<authentication_token>` value from the output:
+
+ ```yaml
+ Name: eks-admin-token-b5zv4
+ Namespace: kube-system
+ Labels: <none>
+ Annotations: kubernetes.io/service-account.name=eks-admin
+ kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8
+
+ Type: kubernetes.io/service-account-token
+
+ Data
+ ====
+ ca.crt: 1025 bytes
+ namespace: 11 bytes
+ token: <authentication_token>
+ ```
1. The API server endpoint is also required, so GitLab can connect to the cluster.
This is displayed on the AWS EKS console, when viewing the EKS cluster details.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 56f8257fbe7..35f2976899b 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -56,8 +56,8 @@ 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.
+ NOTE: **Note:**
+ You need Maintainer [permissions] and above to access the Kubernetes page.
1. Click **Add Kubernetes cluster**.
1. Click **Create with Google Kubernetes Engine**.
@@ -97,117 +97,119 @@ 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.
+ NOTE: **Note:**
+ You need Maintainer [permissions] and above to access the Kubernetes page.
1. Click **Add Kubernetes cluster**.
1. Click **Add an existing Kubernetes cluster** and fill in the details:
- - **Kubernetes cluster name** (required) - The name you wish to give the cluster.
- - **Environment scope** (required) - The
- [associated environment](#setting-the-environment-scope-premium) to this cluster.
- - **API URL** (required) -
- It's the URL that GitLab uses to access the Kubernetes API. Kubernetes
- exposes several APIs, we want the "base" URL that is common to all of them,
- e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`.
-
- Get the API URL by running this command:
-
- ```sh
- kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'
- ```
- - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the EKS cluster. We will use the certificate created by default.
- - List the secrets with `kubectl get secrets`, and one should named similar to
- `default-token-xxxxx`. Copy that token name for use below.
- - Get the certificate by running this command:
-
- ```sh
- kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
- ```
- - **Token** -
- GitLab authenticates against Kubernetes using service tokens, which are
- scoped to a particular `namespace`.
- **The token used should belong to a service account with
- [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
- privileges.** To create this service account:
-
- 1. Create a file called `gitlab-admin-service-account.yaml` with contents:
-
- ```yaml
- apiVersion: v1
- kind: ServiceAccount
- metadata:
- name: gitlab-admin
- namespace: kube-system
- ---
- apiVersion: rbac.authorization.k8s.io/v1beta1
- kind: ClusterRoleBinding
- metadata:
- name: gitlab-admin
- roleRef:
- apiGroup: rbac.authorization.k8s.io
- kind: ClusterRole
- name: cluster-admin
- subjects:
- - kind: ServiceAccount
- name: gitlab-admin
- namespace: kube-system
- ```
-
- 1. Apply the service account and cluster role binding to your cluster:
-
- ```bash
- kubectl apply -f gitlab-admin-service-account.yaml
- ```
-
- Output:
-
- ```bash
- serviceaccount "gitlab-admin" created
- clusterrolebinding "gitlab-admin" created
- ```
-
- 1. Retrieve the token for the `gitlab-admin` service account:
-
- ```bash
- kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')
- ```
-
- Copy the `<authentication_token>` value from the output:
-
- ```yaml
- Name: gitlab-admin-token-b5zv4
- Namespace: kube-system
- Labels: <none>
- Annotations: kubernetes.io/service-account.name=gitlab-admin
- kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8
-
- Type: kubernetes.io/service-account-token
-
- Data
- ====
- ca.crt: 1025 bytes
- namespace: 11 bytes
- token: <authentication_token>
- ```
-
- NOTE: **Note:**
- For GKE clusters, you will need the
- `container.clusterRoleBindings.create` permission to create a cluster
- role binding. You can follow the [Google Cloud
- documentation](https://cloud.google.com/iam/docs/granting-changing-revoking-access)
- to grant access.
-
- - **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](#gitlab-managed-clusters) for more information.
-
- - **Project namespace** (optional) - You don't have to fill it in; by leaving
- it blank, GitLab will create one for you. Also:
- - Each project should have a unique namespace.
- - The project namespace is not necessarily the namespace of the secret, if
- you're using a secret with broader permissions, like the secret from `default`.
- - You should **not** use `default` as the project namespace.
- - If you or someone created a secret specifically for the project, usually
- with limited permissions, the secret's namespace and project namespace may
- be the same.
+ - **Kubernetes cluster name** (required) - The name you wish to give the cluster.
+ - **Environment scope** (required) - The
+ [associated environment](#setting-the-environment-scope-premium) to this cluster.
+ - **API URL** (required) -
+ It's the URL that GitLab uses to access the Kubernetes API. Kubernetes
+ exposes several APIs, we want the "base" URL that is common to all of them,
+ e.g., `https://kubernetes.example.com` rather than `https://kubernetes.example.com/api/v1`.
+
+ Get the API URL by running this command:
+
+ ```sh
+ kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'
+ ```
+
+ - **CA certificate** (required) - A valid Kubernetes certificate is needed to authenticate to the EKS cluster. We will use the certificate created by default.
+ - List the secrets with `kubectl get secrets`, and one should named similar to
+ `default-token-xxxxx`. Copy that token name for use below.
+ - Get the certificate by running this command:
+
+ ```sh
+ kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
+ ```
+
+ - **Token** -
+ GitLab authenticates against Kubernetes using service tokens, which are
+ scoped to a particular `namespace`.
+ **The token used should belong to a service account with
+ [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
+ privileges.** To create this service account:
+
+ 1. Create a file called `gitlab-admin-service-account.yaml` with contents:
+
+ ```yaml
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+ name: gitlab-admin
+ namespace: kube-system
+ ---
+ apiVersion: rbac.authorization.k8s.io/v1beta1
+ kind: ClusterRoleBinding
+ metadata:
+ name: gitlab-admin
+ roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: cluster-admin
+ subjects:
+ - kind: ServiceAccount
+ name: gitlab-admin
+ namespace: kube-system
+ ```
+
+ 1. Apply the service account and cluster role binding to your cluster:
+
+ ```bash
+ kubectl apply -f gitlab-admin-service-account.yaml
+ ```
+
+ Output:
+
+ ```bash
+ serviceaccount "gitlab-admin" created
+ clusterrolebinding "gitlab-admin" created
+ ```
+
+ 1. Retrieve the token for the `gitlab-admin` service account:
+
+ ```bash
+ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')
+ ```
+
+ Copy the `<authentication_token>` value from the output:
+
+ ```yaml
+ Name: gitlab-admin-token-b5zv4
+ Namespace: kube-system
+ Labels: <none>
+ Annotations: kubernetes.io/service-account.name=gitlab-admin
+ kubernetes.io/service-account.uid=bcfe66ac-39be-11e8-97e8-026dce96b6e8
+
+ Type: kubernetes.io/service-account-token
+
+ Data
+ ====
+ ca.crt: 1025 bytes
+ namespace: 11 bytes
+ token: <authentication_token>
+ ```
+
+ NOTE: **Note:**
+ For GKE clusters, you will need the
+ `container.clusterRoleBindings.create` permission to create a cluster
+ role binding. You can follow the [Google Cloud
+ documentation](https://cloud.google.com/iam/docs/granting-changing-revoking-access)
+ to grant access.
+
+ - **GitLab-managed cluster** - Leave this checked if you want GitLab to manage namespaces and service accounts for this cluster. See the [Managed clusters section](#gitlab-managed-clusters) for more information.
+
+ - **Project namespace** (optional) - You don't have to fill it in; by leaving
+ it blank, GitLab will create one for you. Also:
+ - Each project should have a unique namespace.
+ - The project namespace is not necessarily the namespace of the secret, if
+ you're using a secret with broader permissions, like the secret from `default`.
+ - You should **not** use `default` as the project namespace.
+ - If you or someone created a secret specifically for the project, usually
+ with limited permissions, the secret's namespace and project namespace may
+ be the same.
1. Finally, click the **Create Kubernetes cluster** button.
@@ -341,10 +343,15 @@ 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).
## Installing applications
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index c67b12fb91a..c021d059d30 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -1,28 +1,27 @@
# Runbooks
-Runbooks are a collection of documented procedures that explain how to
-carry out a particular process, be it starting, stopping, debugging,
+Runbooks are a collection of documented procedures that explain how to
+carry out a particular process, be it starting, stopping, debugging,
or troubleshooting a particular system.
Using [Jupyter Notebooks](https://jupyter.org/) and the [Rubix library](https://github.com/Nurtch/rubix),
users can get started writing their own executable runbooks.
-
## Overview
-Historically, runbooks took the form of a decision tree or a detailed
-step-by-step guide depending on the condition or system.
+Historically, runbooks took the form of a decision tree or a detailed
+step-by-step guide depending on the condition or system.
-Modern implementations have introduced the concept of an "executable
-runbooks", where, along with a well-defined process, operators can execute
+Modern implementations have introduced the concept of an "executable
+runbooks", where, along with a well-defined process, operators can execute
pre-written code blocks or database queries against a given environment.
## Executable Runbooks
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/45912) in GitLab 11.4.
-The JupyterHub app offered via GitLab’s Kubernetes integration now ships
-with Nurtch’s Rubix library, providing a simple way to create DevOps
+The JupyterHub app offered via GitLab’s Kubernetes integration now ships
+with Nurtch’s Rubix library, providing a simple way to create DevOps
runbooks. A sample runbook is provided, showcasing common operations. While Rubix makes it
simple to create common Kubernetes and AWS workflows, you can also create them manually without
Rubix.
@@ -35,33 +34,33 @@ 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.
+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).
-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
+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.
-1. **Ingress** - Ingress can provide load balancing, SSL termination, and name-based
+1. **Ingress** - Ingress can provide load balancing, SSL termination, and name-based
virtual hosting. It acts as a web proxy for your applications.
-1. **JupyterHub** - [JupyterHub](https://jupyterhub.readthedocs.io/) is a multi-user service for managing notebooks across
- a team. Jupyter Notebooks provide a web-based interactive programming environment
+1. **JupyterHub** - [JupyterHub](https://jupyterhub.readthedocs.io/) is a multi-user service for managing notebooks across
+ a team. Jupyter Notebooks provide a web-based interactive programming environment
used for data analysis, visualization, and machine learning.
## Nurtch
-Nurtch is the company behind the [Rubix library](https://github.com/Nurtch/rubix). Rubix is
-an open-source python library that makes it easy to perform common DevOps tasks inside Jupyter Notebooks.
-Tasks such as plotting Cloudwatch metrics and rolling your ECS/Kubernetes app are simplified
-down to a couple of lines of code. See the [Nurtch Documentation](http://docs.nurtch.com/en/latest)
+Nurtch is the company behind the [Rubix library](https://github.com/Nurtch/rubix). Rubix is
+an open-source python library that makes it easy to perform common DevOps tasks inside Jupyter Notebooks.
+Tasks such as plotting Cloudwatch metrics and rolling your ECS/Kubernetes app are simplified
+down to a couple of lines of code. See the [Nurtch Documentation](http://docs.nurtch.com/en/latest)
for more information.
## Configure an executable runbook with GitLab
-Follow this step-by-step guide to configure an executable runbook in GitLab using
+Follow this step-by-step guide to configure an executable runbook in GitLab using
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 [Adding and creating a new GKE cluster via GitLab](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab)
to add a Kubernetes cluster to your project.
### 2. Install Helm Tiller, Ingress, and JupyterHub
@@ -80,13 +79,13 @@ Once Ingress has been installed successfully, click the **Install** button next
### 3. Login to JupyterHub and start the server
-Once JupyterHub has been installed successfully, navigate to the displayed **Jupyter Hostname** URL and click
-**Sign in with GitLab**. Authentication is automatically enabled for any user of the GitLab instance via OAuth2. This
+Once JupyterHub has been installed successfully, navigate to the displayed **Jupyter Hostname** URL and click
+**Sign in with GitLab**. Authentication is automatically enabled for any user of the GitLab instance via OAuth2. This
will redirect to GitLab in order to authorize JupyterHub to use your GitLab account. Click **Authorize**.
![authorize jupyter](img/authorize-jupyter.png)
-Once the application has been authorized you will taken back to the JupyterHub application. Click **Start My Server**.
+Once the application has been authorized you will taken back to the JupyterHub application. Click **Start My Server**.
The server will take a couple of seconds to start.
### 4. Configure access
@@ -103,7 +102,7 @@ Double-click the "Nurtch-DevOps-Demo.ipynb" runbook.
![sample runbook](img/sample-runbook.png)
-The contents on the runbook will be displayed on the right side of the screen. Under the "Setup" section, you will find
+The contents on the runbook will be displayed on the right side of the screen. Under the "Setup" section, you will find
entries for both your `PRIVATE_TOKEN` and your `PROJECT_ID`. Enter both these values, conserving the single quotes as follows:
```sql
@@ -111,7 +110,7 @@ PRIVATE_TOKEN = 'n671WNGecHugsdEDPsyo'
PROJECT_ID = '1234567'
```
-Update the `VARIABLE_NAME` on the last line of this section to match the name of the variable you are using for your
+Update the `VARIABLE_NAME` on the last line of this section to match the name of the variable you are using for your
access token. In this example our variable name is `PRIVATE_TOKEN`.
```sql
@@ -120,8 +119,8 @@ VARIABLE_VALUE = project.variables.get('PRIVATE_TOKEN').value
### 5. Configure an operation
-For this example we'll use the "**Run SQL queries in Notebook**" section in the sample runbook to query
-a postgres database. The first 4 lines of the section define the variables that are required for this query to function.
+For this example we'll use the "**Run SQL queries in Notebook**" section in the sample runbook to query
+a postgres database. The first 4 lines of the section define the variables that are required for this query to function.
```sql
%env DB_USER={project.variables.get('DB_USER').value}
@@ -134,10 +133,10 @@ Create the matching variables in your project's **Settings >> CI/CD >> Variables
![gitlab variables](img/gitlab-variables.png)
-Back in Jupyter, click the "Run SQL queries in Notebook" heading and the click *Run*. The results will be
+Back in Jupyter, click the "Run SQL queries in Notebook" heading and the click *Run*. The results will be
displayed in-line as follows:
![postgres query](img/postgres-query.png)
-You can try other operations such as running shell scripts or interacting with a Kubernetes cluster. Visit the
-[Nurtch Documentation](http://docs.nurtch.com/) for more information. \ No newline at end of file
+You can try other operations such as running shell scripts or interacting with a Kubernetes cluster. Visit the
+[Nurtch Documentation](http://docs.nurtch.com/) for more information.
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index a06c3d3c662..a32759c7bdc 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -24,34 +24,33 @@ To run Knative on Gitlab, you will need:
1. **Existing GitLab project:** You will need a GitLab project to associate all resources. The simplest way to get started:
- - If you are planning on deploying functions, clone the [functions example project](https://gitlab.com/knative-examples/functions) to get started.
- - 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.
+ - If you are planning on deploying functions, clone the [functions example project](https://gitlab.com/knative-examples/functions) to get started.
+ - 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 set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory.
+ 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 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.
+ Knative.
1. **GitLab Runner:** A runner is required to run the CI jobs that will deploy serverless
- applications or functions onto your cluster. You can install the GitLab Runner
- onto the existing Kubernetes cluster. See [Installing Applications](../index.md#installing-applications) for more information.
+ applications or functions onto your cluster. You can install the GitLab Runner
+ onto the existing Kubernetes cluster. See [Installing Applications](../index.md#installing-applications) for more information.
1. **Domain Name:** Knative will provide its own load balancer using Istio. It will provide an
- external IP address or hostname for all the applications served by Knative. You will be prompted to enter a
- wildcard domain where your applications will be served. Configure your DNS server to use the
- external IP address or hostname for that domain.
+ external IP address or hostname for all the applications served by Knative. You will be prompted to enter a
+ wildcard domain where your applications will be served. Configure your DNS server to use the
+ 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.
+ to build the application. We also use [gitlabktl](https://gitlab.com/gitlab-org/gitlabktl)
+ 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.
+ will contain the information for all the functions being hosted in the repository as well as a reference to the
+ runtime being used.
1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications): Knative requires a
- `Dockerfile` in order to build your applications. It should be included at the root of your
- project's repo and expose port `8080`. `Dockerfile` is not require if you plan to build serverless functions
- using our [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes).
+ `Dockerfile` in order to build your applications. It should be included at the root of your
+ project's repo and expose port `8080`. `Dockerfile` is not require if you plan to build serverless functions
+ using our [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes).
1. **Prometheus** (optional): Installing Prometheus allows you to monitor the scale and traffic of your serverless function/application.
- See [Installing Applications](../index.md#installing-applications) for more information.
+ See [Installing Applications](../index.md#installing-applications) for more information.
## Installing Knative via GitLab's Kubernetes integration
@@ -60,9 +59,9 @@ The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.
1. [Add a Kubernetes cluster](../index.md) and [install Helm](../index.md#installing-applications).
1. Once Helm has been successfully installed, scroll down to the Knative app section. Enter the domain to be used with
- your application/functions (e.g. `example.com`) and click **Install**.
+ your application/functions (e.g. `example.com`) and click **Install**.
- ![install-knative](img/install-knative.png)
+ ![install-knative](img/install-knative.png)
1. After the Knative installation has finished, you can wait for the IP address or hostname to be displayed in the
**Knative Endpoint** field or [retrieve the Istio Ingress Endpoint manually](../#manually-determining-the-external-endpoint).
@@ -77,7 +76,7 @@ The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22.
if your Knative base domain is `knative.info` then you need to create an A record or CNAME record with domain `*.knative.info`
pointing the ip address or hostname of the ingress.
- ![dns entry](img/dns-entry.png)
+ ![dns entry](img/dns-entry.png)
NOTE: **Note:**
You can deploy either [functions](#deploying-functions) or [serverless applications](#deploying-serverless-applications)
@@ -100,48 +99,54 @@ You must do the following:
[add an existing Kubernetes cluster](../index.md#adding-an-existing-kubernetes-cluster).
1. Ensure GitLab can manage Knative:
- - For a non-GitLab managed cluster, ensure that the service account for the token
- provided can manage resources in the `serving.knative.dev` API group.
- - For a GitLab managed cluster,
- GitLab uses a service account with the `edit` cluster role. This account needs
- the ability to manage resources in the `serving.knative.dev` API group.
- We suggest you do this with an [aggregated ClusterRole](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles)
- adding rules to the default `edit` cluster role:
- First, save the following YAML as `knative-serving-only-role.yaml`:
-
- ```yaml
- apiVersion: rbac.authorization.k8s.io/v1
- kind: ClusterRole
- metadata:
- name: knative-serving-only-role
- labels:
- rbac.authorization.k8s.io/aggregate-to-edit: "true"
- rules:
- - apiGroups:
- - serving.knative.dev
- resources:
- - configurations
- - configurationgenerations
- - routes
- - revisions
- - revisionuids
- - autoscalers
- - services
- verbs:
- - get
- - list
- - create
- - update
- - delete
- - patch
- - watch
- ```
-
- Then run the following command:
-
- ```bash
- kubectl apply -f knative-serving-only-role.yaml
- ```
+ - For a non-GitLab managed cluster, ensure that the service account for the token
+ provided can manage resources in the `serving.knative.dev` API group.
+ - For a GitLab managed cluster, if you added the cluster in [GitLab 12.1 or later](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30235),
+ then GitLab will already have the required access and you can proceed to the next step.
+
+ Otherwise, you need to manually grant GitLab's service account the ability to manage
+ resources in the `serving.knative.dev` API group. Since every GitLab service account
+ has the `edit` cluster role, the simplest way to do this is with an
+ [aggregated ClusterRole](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles)
+ adding rules to the default `edit` cluster role: First, save the following YAML as
+ `knative-serving-only-role.yaml`:
+
+ ```yaml
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: knative-serving-only-role
+ labels:
+ rbac.authorization.k8s.io/aggregate-to-edit: "true"
+ rules:
+ - apiGroups:
+ - serving.knative.dev
+ resources:
+ - configurations
+ - configurationgenerations
+ - routes
+ - revisions
+ - revisionuids
+ - autoscalers
+ - services
+ verbs:
+ - get
+ - list
+ - create
+ - update
+ - delete
+ - patch
+ - watch
+ ```
+
+ Then run the following command:
+
+ ```bash
+ kubectl apply -f knative-serving-only-role.yaml
+ ```
+
+ If you would rather grant permissions on a per service account basis, you can do this
+ using a `Role` and `RoleBinding` specific to the service account and namespace.
1. Follow the steps to deploy [functions](#deploying-functions)
or [serverless applications](#deploying-serverless-applications) onto your
@@ -151,9 +156,9 @@ You must do the following:
> Introduced in GitLab 11.6.
-Using functions is useful for dealing with independent
-events without needing to maintain a complex unified infrastructure. This allows
-you to focus on a single task that can be executed/scaled automatically and independently.
+Using functions is useful for dealing with independent events without needing
+to maintain a complex unified infrastructure. This allows you to focus on a
+single task that can be executed/scaled automatically and independently.
Currently the following [runtimes](https://gitlab.com/gitlab-org/serverless/runtimes) are offered:
@@ -161,15 +166,21 @@ Currently the following [runtimes](https://gitlab.com/gitlab-org/serverless/runt
- node.js
- Dockerfile
-You can find and import all the files referenced in this doc in the **[functions example project](https://gitlab.com/knative-examples/functions)**.
+`Dockerfile` presence is assumed when a runtime is not specified.
-Follow these steps to deploy a function using the Node.js runtime to your Knative instance (you can skip these steps if you've cloned the example project):
+You can find and import all the files referenced in this doc in the
+**[functions example project](https://gitlab.com/knative-examples/functions)**.
-1. Create a directory that will house the function. In this example we will create a directory called `echo` at the root of the project.
+Follow these steps to deploy a function using the Node.js runtime to your
+Knative instance (you can skip these steps if you've cloned the example
+project):
+
+1. Create a directory that will house the function. In this example we will
+ create a directory called `echo` at the root of the project.
1. Create the file that will contain the function code. In this example, our file is called `echo.js` and is located inside the `echo` directory. If your project is:
- - Public, continue to the next step.
- - Private, you will need to [create a GitLab deploy token](../../deploy_tokens/index.md#creating-a-deploy-token) with `gitlab-deploy-token` as the name and the `read_registry` scope.
+ - Public, continue to the next step.
+ - Private, you will need to [create a GitLab deploy token](../../deploy_tokens/index.md#creating-a-deploy-token) with `gitlab-deploy-token` as the name and the `read_registry` scope.
1. `.gitlab-ci.yml`: this defines a pipeline used to deploy your functions.
It must be included at the root of your repository:
@@ -187,16 +198,16 @@ Follow these steps to deploy a function using the Node.js runtime to your Knativ
environment: production
```
- This `.gitlab-ci.yml` creates jobs that invoke some predefined commands to
- build and deploy your functions to your cluster.
+ This `.gitlab-ci.yml` creates jobs that invoke some predefined commands to
+ build and deploy your functions to your cluster.
- `Serverless.gitlab-ci.yml` is a template that allows customization.
- You can either import it with `include` parameter and use `extends` to
- customize your jobs, or you can inline the entire template by choosing it
- from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through
- the user interface.
+ `Serverless.gitlab-ci.yml` is a template that allows customization.
+ You can either import it with `include` parameter and use `extends` to
+ customize your jobs, or you can inline the entire template by choosing it
+ from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through
+ the user interface.
-2. `serverless.yml`: this file contains the metadata for your functions,
+1. `serverless.yml`: this file contains the metadata for your functions,
such as name, runtime, and environment.
It must be included at the root of your repository.
@@ -237,26 +248,27 @@ 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`
-In the `serverless.yml` example above, the function name is `echo` and the subsequent lines contain the function attributes.
+In the `serverless.yml` example above, the function name is `echo` and the
+subsequent lines contain the function attributes.
| Parameter | Description |
|-----------|-------------|
| `handler` | The function's name. |
| `source` | Directory with sources of a functions. |
-| `runtime` | The runtime to be used to execute the function. |
+| `runtime` (optional)| The runtime to be used to execute the function. When the runtime is not specified, we assume that `Dockerfile` is present in the function directory specified by `source`. |
| `description` | A short description of the function. |
| `environment` | Sets an environment variable for the specific function only. |
-After the `gitlab-ci.yml` template has been added and the `serverless.yml` file has been
-created, pushing a commit to your project will result in a
-CI pipeline being executed which will deploy each function as a Knative service.
-Once the deploy stage has finished, additional details for the function will
-appear under **Operations > Serverless**.
+After the `gitlab-ci.yml` template has been added and the `serverless.yml` file
+has been created, pushing a commit to your project will result in a CI pipeline
+being executed which will deploy each function as a Knative service. Once the
+deploy stage has finished, additional details for the function will appear
+under **Operations > Serverless**.
![serverless page](img/serverless-page.png)
@@ -275,16 +287,17 @@ The sample function can now be triggered from any HTTP client using a simple `PO
1. Using curl (replace the URL on the last line with the URL of your application):
- ```bash
- curl \
- --header "Content-Type: application/json" \
- --request POST \
- --data '{"GitLab":"FaaS"}' \
- http://functions-echo.functions-1.functions.example.com/
- ```
- 2. Using a web-based tool (ie. postman, restlet, etc)
+ ```bash
+ curl \
+ --header "Content-Type: application/json" \
+ --request POST \
+ --data '{"GitLab":"FaaS"}' \
+ http://functions-echo.functions-1.functions.example.com/
+ ```
+
+ 1. Using a web-based tool (ie. postman, restlet, etc)
- ![function execution](img/function-execution.png)
+ ![function execution](img/function-execution.png)
## Deploying Serverless applications
@@ -313,6 +326,8 @@ customize your jobs, or you can inline the entire template by choosing it
from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through
the user interface.
+A `serverless.yml` file is not required when deploying serverless applications.
+
### Deploy the application with Knative
With all the pieces in place, the next time a CI pipeline runs, the Knative application will be deployed. Navigate to
@@ -327,27 +342,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)
@@ -376,269 +387,268 @@ cluster.
By default, a GitLab serverless deployment will be served over `http`. In order to serve over `https` you
must manually obtain and install TLS certificates.
-The simplest way to accomplish this is to
+The simplest way to accomplish this is to
use [Certbot to manually obtain Let's Encrypt certificates](https://knative.dev/docs/serving/using-a-tls-cert/#using-certbot-to-manually-obtain-let-s-encrypt-certificates). Certbot is a free, open source software tool for automatically using Let’s Encrypt certificates on manually-administrated websites to enable HTTPS.
NOTE: **Note:**
The instructions below relate to installing and running Certbot on a Linux server and may not work on other operating systems.
-1. Install Certbot by running the
+1. Install Certbot by running the
[`certbot-auto` wrapper script](https://certbot.eff.org/docs/install.html#certbot-auto).
On the command line of your server, run the following commands:
- ```sh
- wget https://dl.eff.org/certbot-auto
- sudo mv certbot-auto /usr/local/bin/certbot-auto
- sudo chown root /usr/local/bin/certbot-auto
- chmod 0755 /usr/local/bin/certbot-auto
- /usr/local/bin/certbot-auto --help
- ```
-
- To check the integrity of the `certbot-auto` script, run:
-
- ```sh
- wget -N https://dl.eff.org/certbot-auto.asc
- gpg2 --keyserver ipv4.pool.sks-keyservers.net --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2
- gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc /usr/local/bin/certbot-auto
- ```
-
- The output of the last command should look something like:
-
- ```sh
- gpg: Signature made Mon 10 Jun 2019 06:24:40 PM EDT
- gpg: using RSA key A2CFB51FA275A7286234E7B24D17C995CD9775F2
- gpg: key 4D17C995CD9775F2 marked as ultimately trusted
- gpg: checking the trustdb
- gpg: marginals needed: 3 completes needed: 1 trust model: pgp
- gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
- gpg: next trustdb check due at 2027-11-22
- gpg: Good signature from "Let's Encrypt Client Team <letsencrypt-client@eff.org>" [ultimate]
- ```
+ ```sh
+ wget https://dl.eff.org/certbot-auto
+ sudo mv certbot-auto /usr/local/bin/certbot-auto
+ sudo chown root /usr/local/bin/certbot-auto
+ chmod 0755 /usr/local/bin/certbot-auto
+ /usr/local/bin/certbot-auto --help
+ ```
+
+ To check the integrity of the `certbot-auto` script, run:
+
+ ```sh
+ wget -N https://dl.eff.org/certbot-auto.asc
+ gpg2 --keyserver ipv4.pool.sks-keyservers.net --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2
+ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc /usr/local/bin/certbot-auto
+ ```
+
+ The output of the last command should look something like:
+
+ ```sh
+ gpg: Signature made Mon 10 Jun 2019 06:24:40 PM EDT
+ gpg: using RSA key A2CFB51FA275A7286234E7B24D17C995CD9775F2
+ gpg: key 4D17C995CD9775F2 marked as ultimately trusted
+ gpg: checking the trustdb
+ gpg: marginals needed: 3 completes needed: 1 trust model: pgp
+ gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
+ gpg: next trustdb check due at 2027-11-22
+ gpg: Good signature from "Let's Encrypt Client Team <letsencrypt-client@eff.org>" [ultimate]
+ ```
1. Run the following command to use Certbot to request a certificate
using DNS challenge during authorization:
+ ```sh
+ ./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
+ `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.
+
+ ![function_endpoint](img/function-endpoint.png)
+
+ In the above image, the namespace for the project is `node-function-11909507` and the domain is `knative.info`, thus
+ certificate request line would look like this:
+
+ ```sh
+ ./certbot-auto certonly --manual --preferred-challenges dns -d '*.node-function-11909507.knative.info'
+ ```
- ```sh
- ./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
- `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.
-
- ![function_endpoint](img/function-endpoint.png)
-
- In the above image, the namespace for the project is `node-function-11909507` and the domain is `knative.info`, thus
- certificate request line would look like this:
-
- ```sh
- ./certbot-auto certonly --manual --preferred-challenges dns -d '*.node-function-11909507.knative.info'
- ```
-
- The Certbot tool walks you through the steps of validating that you own each domain that you specify by creating TXT records in those domains.
- After this process is complete, the output should look something like this:
-
- ```sh
- IMPORTANT NOTES:
- - Congratulations! Your certificate and chain have been saved at:
- /etc/letsencrypt/live/namespace.example.com/fullchain.pem
- Your key file has been saved at:
- /etc/letsencrypt/live/namespace.example/privkey.pem
- Your cert will expire on 2019-09-19. To obtain a new or tweaked
- version of this certificate in the future, simply run certbot-auto
- again. To non-interactively renew *all* of your certificates, run
- "certbot-auto renew"
- -----BEGIN PRIVATE KEY-----
- - Your account credentials have been saved in your Certbot
- configuration directory at /etc/letsencrypt. You should make a
- secure backup of this folder now. This configuration directory will
- also contain certificates and private keys obtained by Certbot so
- making regular backups of this folder is ideal.
- ```
+ The Certbot tool walks you through the steps of validating that you own each domain that you specify by creating TXT records in those domains.
+ After this process is complete, the output should look something like this:
+
+ ```sh
+ IMPORTANT NOTES:
+ - Congratulations! Your certificate and chain have been saved at:
+ /etc/letsencrypt/live/namespace.example.com/fullchain.pem
+ Your key file has been saved at:
+ /etc/letsencrypt/live/namespace.example/privkey.pem
+ Your cert will expire on 2019-09-19. To obtain a new or tweaked
+ version of this certificate in the future, simply run certbot-auto
+ again. To non-interactively renew *all* of your certificates, run
+ "certbot-auto renew"
+ -----BEGIN PRIVATE KEY-----
+ - Your account credentials have been saved in your Certbot
+ configuration directory at /etc/letsencrypt. You should make a
+ secure backup of this folder now. This configuration directory will
+ also contain certificates and private keys obtained by Certbot so
+ making regular backups of this folder is ideal.
+ ```
1. Create certificate and private key files. Using the contents of the files
returned by Certbot, we'll create two files in order to create the
Kubernetes secret:
- Run the following command to see the contents of `fullchain.pem`:
-
- ```sh
- sudo cat /etc/letsencrypt/live/node-function-11909507.knative.info/fullchain.pem
- ```
-
- Output should look like this:
-
- ```sh
- -----BEGIN CERTIFICATE-----
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b4ag==
- -----END CERTIFICATE-----
- -----BEGIN CERTIFICATE-----
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- K2fcb195768c39e9a94cec2c2e30Qg==
- -----END CERTIFICATE-----
- ```
-
- Create a file with the name `cert.pem` with the contents of the entire output.
-
- Once `cert.pem` is created, run the following command to see the contents of `privkey.pem`:
-
- ```sh
- sudo cat /etc/letsencrypt/live/namespace.example/privkey.pem
- ```
-
- Output should look like this:
-
- ```sh
- -----BEGIN PRIVATE KEY-----
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
- -----BEGIN CERTIFICATE-----
- fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
- 4f294d1eaca42b8692017b4262==
- -----END PRIVATE KEY-----
- ```
-
- Create a new file with the name `cert.pk` with the contents of the entire output.
+ Run the following command to see the contents of `fullchain.pem`:
+
+ ```sh
+ sudo cat /etc/letsencrypt/live/node-function-11909507.knative.info/fullchain.pem
+ ```
+
+ Output should look like this:
+
+ ```sh
+ -----BEGIN CERTIFICATE-----
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b4ag==
+ -----END CERTIFICATE-----
+ -----BEGIN CERTIFICATE-----
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ K2fcb195768c39e9a94cec2c2e30Qg==
+ -----END CERTIFICATE-----
+ ```
+
+ Create a file with the name `cert.pem` with the contents of the entire output.
+
+ Once `cert.pem` is created, run the following command to see the contents of `privkey.pem`:
+
+ ```sh
+ sudo cat /etc/letsencrypt/live/namespace.example/privkey.pem
+ ```
+
+ Output should look like this:
+
+ ```sh
+ -----BEGIN PRIVATE KEY-----
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ 2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df
+ -----BEGIN CERTIFICATE-----
+ fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6
+ 4f294d1eaca42b8692017b4262==
+ -----END PRIVATE KEY-----
+ ```
+
+ Create a new file with the name `cert.pk` with the contents of the entire output.
1. Create a Kubernetes secret to hold your TLS certificate, `cert.pem`, and
the private key `cert.pk`:
- NOTE: **Note:**
- Running `kubectl` commands on your cluster requires setting up access to the cluster first.
- For clusters created on GKE, see
- [GKE Cluster Access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl).
- For other platforms, [install `kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
-
- ```sh
- kubectl create --namespace istio-system secret tls istio-ingressgateway-certs \
- --key cert.pk \
- --cert cert.pem
- ```
+ NOTE: **Note:**
+ Running `kubectl` commands on your cluster requires setting up access to the cluster first.
+ For clusters created on GKE, see
+ [GKE Cluster Access](https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl).
+ For other platforms, [install `kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
+
+ ```sh
+ kubectl create --namespace istio-system secret tls istio-ingressgateway-certs \
+ --key cert.pk \
+ --cert cert.pem
+ ```
- Where `cert.pem` and `cert.pk` are your certificate and private key files. Note that the `istio-ingressgateway-certs` secret name is required.
+ Where `cert.pem` and `cert.pk` are your certificate and private key files. Note that the `istio-ingressgateway-certs` secret name is required.
1. Configure Knative to use the new secret that you created for HTTPS
- connections. Run the
- following command to open the Knative shared `gateway` in edit mode:
-
- ```sh
- kubectl edit gateway knative-ingress-gateway --namespace knative-serving
- ```
-
- Update the gateway to include the following tls: section and configuration:
-
- ```sh
- tls:
- mode: SIMPLE
- privateKey: /etc/istio/ingressgateway-certs/tls.key
- serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
- ```
-
- Example:
-
- ```sh
- apiVersion: networking.istio.io/v1alpha3
- kind: Gateway
- metadata:
- # ... skipped ...
- spec:
- selector:
- istio: ingressgateway
- servers:
- - hosts:
- - "*"
- port:
- name: http
- number: 80
- protocol: HTTP
- - hosts:
- - "*"
- port:
- name: https
- number: 443
- protocol: HTTPS
- tls:
- mode: SIMPLE
- privateKey: /etc/istio/ingressgateway-certs/tls.key
- serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
- ```
-
- After your changes are running on your Knative cluster, you can begin using the HTTPS protocol for secure access your deployed Knative services.
- In the event a mistake is made during this process and you need to update the cert, you will need to edit the gateway `knative-ingress-gateway`
- to switch back to `PASSTHROUGH` mode. Once corrections are made, edit the file again so the gateway will use the new certificates. \ No newline at end of file
+ connections. Run the
+ following command to open the Knative shared `gateway` in edit mode:
+
+ ```sh
+ kubectl edit gateway knative-ingress-gateway --namespace knative-serving
+ ```
+
+ Update the gateway to include the following tls: section and configuration:
+
+ ```sh
+ tls:
+ mode: SIMPLE
+ privateKey: /etc/istio/ingressgateway-certs/tls.key
+ serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
+ ```
+
+ Example:
+
+ ```sh
+ apiVersion: networking.istio.io/v1alpha3
+ kind: Gateway
+ metadata:
+ # ... skipped ...
+ spec:
+ selector:
+ istio: ingressgateway
+ servers:
+ - hosts:
+ - "*"
+ port:
+ name: http
+ number: 80
+ protocol: HTTP
+ - hosts:
+ - "*"
+ port:
+ name: https
+ number: 443
+ protocol: HTTPS
+ tls:
+ mode: SIMPLE
+ privateKey: /etc/istio/ingressgateway-certs/tls.key
+ serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
+ ```
+
+ After your changes are running on your Knative cluster, you can begin using the HTTPS protocol for secure access your deployed Knative services.
+ In the event a mistake is made during this process and you need to update the cert, you will need to edit the gateway `knative-ingress-gateway`
+ to switch back to `PASSTHROUGH` mode. Once corrections are made, edit the file again so the gateway will use the new certificates.
diff --git a/doc/user/project/code_owners.md b/doc/user/project/code_owners.md
index 78ffa11d59b..96c4f16fe04 100644
--- a/doc/user/project/code_owners.md
+++ b/doc/user/project/code_owners.md
@@ -57,8 +57,8 @@ Example `CODEOWNERS` file:
# Files with a `#` can still be accesssed by escaping the pound sign
\#file_with_pound.rb @owner-file-with-pound
-# Multiple codeowners can be specified, separated by whitespace
-CODEOWNERS @multiple @owners @tab-separated
+# Multiple codeowners can be specified, separated by spaces or tabs
+CODEOWNERS @multiple @code @owners
# Both usernames or email addresses can be used to match
# users. Everything else will be ignored. For example this will
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index eac7fc6b195..9368c56004d 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -241,10 +241,10 @@ The following installation instructions assume you are running Ubuntu:
Enter <kbd>CTRL</kbd>-<kbd>C</kbd> to quit.
1. Install the certificate from `~/.mitmproxy` to your system:
- ```sh
- sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt
- sudo update-ca-certificates
- ```
+ ```sh
+ sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt
+ sudo update-ca-certificates
+ ```
If successful, the output should indicate that a certificate was added:
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index 5d36e1d4be3..6707b88c317 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -21,19 +21,19 @@ Analytics** tab.
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)
+ - Time to schedule an issue (by milestone or by adding it to an issue board)
- **Plan** (Board)
- - Time to first commit
+ - Time to first commit
- **Code** (IDE)
- - Time to create a merge request
+ - Time to create a merge request
- **Test** (CI)
- - Time it takes GitLab CI/CD to test your code
+ - Time it takes GitLab CI/CD to test your code
- **Review** (Merge Request/MR)
- - Time spent on code review
+ - Time spent on code review
- **Staging** (Continuous Deployment)
- - Time between merging and deploying to production
+ - Time between merging and deploying to production
- **Production** (Total)
- - Total lifecycle time; i.e. the velocity of the project or team
+ - Total lifecycle time; i.e. the velocity of the project or team
## How the data is measured
diff --git a/doc/user/project/deploy_boards.md b/doc/user/project/deploy_boards.md
index cb1faa771bc..0a83f2e894a 100644
--- a/doc/user/project/deploy_boards.md
+++ b/doc/user/project/deploy_boards.md
@@ -63,12 +63,12 @@ To display the Deploy Boards for a specific [environment] you should:
1. Have a Kubernetes cluster up and running.
- NOTE: **Running on OpenShift:**
- If you are using OpenShift, ensure that you're using the `Deployment` resource
- instead of `DeploymentConfiguration`, otherwise the Deploy Boards won't render
- correctly. For more information, read the
- [OpenShift docs](https://docs.openshift.com/container-platform/3.7/dev_guide/deployments/kubernetes_deployments.html#kubernetes-deployments-vs-deployment-configurations)
- and [GitLab issue #4584](https://gitlab.com/gitlab-org/gitlab-ee/issues/4584).
+ NOTE: **Running on OpenShift:**
+ If you are using OpenShift, ensure that you're using the `Deployment` resource
+ instead of `DeploymentConfiguration`, otherwise the Deploy Boards won't render
+ correctly. For more information, read the
+ [OpenShift docs](https://docs.openshift.com/container-platform/3.7/dev_guide/deployments/kubernetes_deployments.html#kubernetes-deployments-vs-deployment-configurations)
+ and [GitLab issue #4584](https://gitlab.com/gitlab-org/gitlab-ee/issues/4584).
1. [Configure GitLab Runner][runners] with the [Docker][docker-exec] or
[Kubernetes][kube-exec] executor.
@@ -93,7 +93,7 @@ To display the Deploy Boards for a specific [environment] you should:
To migrate, please apply the required annotations (see above) and
re-deploy your application.
- ![Deploy Boards Kubernetes Label](img/deploy_boards_kubernetes_label.png)
+ ![Deploy Boards Kubernetes Label](img/deploy_boards_kubernetes_label.png)
Once all of the above are set up and the pipeline has run at least once,
navigate to the environments page under **Operations > Environments**.
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index 72594733cd3..164d1dddff0 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -56,9 +56,9 @@ To download a repository using a Deploy Token, you just need to:
1. Take note of your `username` and `token`.
1. `git clone` the project using the Deploy Token:
- ```sh
- git clone http://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git
- ```
+ ```sh
+ git clone http://<username>:<deploy_token>@gitlab.example.com/tanuki/awesome_project.git
+ ```
Replace `<username>` and `<deploy_token>` with the proper values.
diff --git a/doc/user/project/img/autocomplete_characters_example1_v12_0.png b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
new file mode 100644
index 00000000000..9c6fa923b80
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..b2e8a782a0b
--- /dev/null
+++ b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
Binary files differ
diff --git a/doc/user/project/import/bitbucket_server.md b/doc/user/project/import/bitbucket_server.md
index d51a0c0ccca..e4eb1a9a392 100644
--- a/doc/user/project/import/bitbucket_server.md
+++ b/doc/user/project/import/bitbucket_server.md
@@ -24,9 +24,8 @@ Import your projects from Bitbucket Server to GitLab with minimal effort.
1. Currently GitLab doesn't allow comments on arbitrary lines of code, so any
Bitbucket comments out of bounds will be inserted as comments in the merge
request.
-1. Bitbucket Server allows multiple levels of threading. GitLab
- import will collapse this into one discussion and quote part of the original
- comment.
+1. Bitbucket Server allows multiple levels of threading. GitLab import
+ will collapse this into one thread and quote part of the original comment.
1. Declined pull requests have unreachable commits, which prevents the GitLab
importer from generating a proper diff. These pull requests will show up as
empty changes.
diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md
index 334be713aa5..9d7463416d8 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -34,7 +34,7 @@ This approach assumes all users from the self-hosted instance have already been
If the users haven't been migrated yet, the user conducting the import
will take the place of all references to the missing user(s).
-If you need to migrate all data over, you can leverage our [api](../../../api/README.md) to migrate from self-hosted to GitLab.com.
+If you need to migrate all data over, you can leverage our [api](../../../api/README.md) to migrate from self-hosted to GitLab.com.
The order of assets to migrate from a self-hosted instance to GitLab is the following:
1. [Users](../../../api/users.md)
@@ -54,5 +54,5 @@ perhaps from an old server to a new server for example, is to
[back up the project](../../../raketasks/backup_restore.md),
then restore it on the new server.
-In the event of merging two GitLab instances together (for example, both instances have existing data on them and one can't be wiped),
+In the event of merging two GitLab instances together (for example, both instances have existing data on them and one can't be wiped),
refer to the instructions in [Migrating from self-hosted GitLab to GitLab.com](#migrating-from-self-hosted-gitlab-to-gitlabcom).
diff --git a/doc/user/project/import/phabricator.md b/doc/user/project/import/phabricator.md
index 5c624e3aff6..b8f89caba24 100644
--- a/doc/user/project/import/phabricator.md
+++ b/doc/user/project/import/phabricator.md
@@ -15,6 +15,12 @@ Currently, only the following basic fields are imported:
- Created at
- Closed at
+## Users
+
+The assignee and author of a user are deducted from a Task's owner and
+author: If a user with the same username has access to the namespace
+of the project being imported into, then the user will be linked.
+
## Enabling this feature
While this feature is incomplete, a feature flag is required to enable it so that
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/index.md b/doc/user/project/index.md
index f332281fa82..45e96437517 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -52,6 +52,9 @@ When you create a project in GitLab, you'll have access to a large number of
templates for issue and merge request description fields for your project
- [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for
common actions on issues or merge requests
+- [Autocomplete characters](autocomplete_characters.md): Autocomplete
+ references to users, groups, issues, merge requests, and other GitLab
+ elements.
- [Web IDE](web_ide/index.md)
**GitLab CI/CD:**
@@ -63,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
@@ -221,7 +224,7 @@ There are numerous [APIs](../../api/README.md) to use with your projects:
- [Badges](../../api/project_badges.md)
- [Clusters](../../api/project_clusters.md)
-- [Discussions](../../api/discussions.md)
+- [Threads](../../api/discussions.md)
- [General](../../api/projects.md)
- [Import/export](../../api/project_import_export.md)
- [Issue Board](../../api/boards.md)
diff --git a/doc/user/project/integrations/custom_issue_tracker.md b/doc/user/project/integrations/custom_issue_tracker.md
index 23f1ce7a15a..7c7263704f9 100644
--- a/doc/user/project/integrations/custom_issue_tracker.md
+++ b/doc/user/project/integrations/custom_issue_tracker.md
@@ -17,6 +17,6 @@ Once you have configured and enabled Custom Issue Tracker Service you'll see a l
## Referencing issues
-- Issues are referenced with `ANYTHING-<ID>`, where `ANYTHING` can be any string and `<ID>` is a number used in the target project of the custom integration (example `PROJECT-143`).
+- Issues are referenced with `ANYTHING-<ID>`, where `ANYTHING` can be any string and `<ID>` is a number used in the target project of the custom integration (example `PROJECT-143`).
- `ANYTHING` is a placeholder to differentiate against GitLab issues, which are referenced with `#<ID>`. You can use a project name or project key to replace it for example.
-- So with the example above, `PROJECT-143` would refer to `https://customissuetracker.com/project-name/143`. \ No newline at end of file
+- So with the example above, `PROJECT-143` would refer to `https://customissuetracker.com/project-name/143`.
diff --git a/doc/user/project/integrations/gitlab_slack_application.md b/doc/user/project/integrations/gitlab_slack_application.md
index 8e062ca627b..91de64be1fa 100644
--- a/doc/user/project/integrations/gitlab_slack_application.md
+++ b/doc/user/project/integrations/gitlab_slack_application.md
@@ -1,4 +1,4 @@
-# GitLab Slack application **[FREE ONLY]**
+# GitLab Slack application **(FREE ONLY)**
NOTE: **Note:**
The GitLab Slack application is only configurable for GitLab.com. It will **not**
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..7260b11f07b
--- /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..ce4c54f909d
--- /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/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index 32991714973..5116cbfe9ac 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -13,46 +13,46 @@ access to your Jira projects. This is covered in the process below.
1. Log in to your Jira instance as an administrator and under **Jira Administration**
go to **User Management** to create a new user.
- ![Jira user management link](img/jira_user_management_link.png)
+ ![Jira user management link](img/jira_user_management_link.png)
1. The next step is to create a new user (e.g., `gitlab`) who has write access
to projects in Jira. Enter the user's name and a _valid_ e-mail address
since Jira sends a verification e-mail to set up the password.
_**Note:** Jira creates the username automatically by using the e-mail
prefix. You can change it later, if needed. Our integration does not support SSO (such as SAML). You will need to create
- an HTTP basic authentication password. You can do this by visiting the user
- profile, looking up the username, and setting a password._
+ an HTTP basic authentication password. You can do this by visiting the user
+ profile, looking up the username, and setting a password._
- ![Jira create new user](img/jira_create_new_user.png)
+ ![Jira create new user](img/jira_create_new_user.png)
1. Create a `gitlab-developers` group. (We will give this group write access to Jira
projects in a later step). Go to the **Groups** tab on the left, and select **Add group**.
- ![Jira create new user](img/jira_create_new_group.png)
+ ![Jira create new user](img/jira_create_new_group.png)
- Give it a name and click **Add group**.
+ Give it a name and click **Add group**.
1. Add the `gitlab` user to the `gitlab-developers` group by clicking **Edit members**.
The `gitlab-developers` group should be listed in the leftmost box as a selected group.
Under **Add members to selected group(s)**, enter `gitlab`.
- ![Jira add user to group](img/jira_add_user_to_group.png)
-
+ ![Jira add user to group](img/jira_add_user_to_group.png)
+
Click **Add selected users** and `gitlab` should appear in the **Group member(s)** box.
This membership is saved automatically.
-
- ![Jira added user to group](img/jira_added_user_to_group.png)
+
+ ![Jira added user to group](img/jira_added_user_to_group.png)
1. To give the newly-created group 'write' access, you need to create a **Permission Scheme**.
To do this, click the gear icon and select **Issues**. Then click **Permission Schemes**.
Click **Add Permission Scheme** and enter a **Name** and, optionally, a **Description**.
-
+
1. Once your permission scheme is created, you'll be taken back to the permissions scheme list.
- Locate your new permissions scheme and click **Permissions**. Next to **Administer Projects**,
+ Locate your new permissions scheme and click **Permissions**. Next to **Administer Projects**,
click **Edit**. In the resulting dialog box, select **Group** and select `gitlab-developers`
from the dropdown.
- ![Jira group access](img/jira_group_access.png)
+ ![Jira group access](img/jira_group_access.png)
The Jira configuration is complete. Write down the new Jira username and its
password as they will be needed when [configuring GitLab in the next section](jira.md#configuring-gitlab).
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/project_services.md b/doc/user/project/integrations/project_services.md
index 62e08a183f7..67274e8d924 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -20,10 +20,10 @@ Below, you will find a list of the currently supported ones accompanied with com
Click on the service links to see further configuration instructions and details.
-| Service | Description |
+| Service | Description |
| ------- | ----------- |
-| Asana | Asana - Teamwork without email |
-| Assembla | Project Management Software (Source Commits Endpoint) |
+| Asana | Asana - Teamwork without email |
+| Assembla | Project Management Software (Source Commits Endpoint) |
| [Atlassian Bamboo CI](bamboo.md) | A continuous integration and build server |
| Buildkite | Continuous integration and deployments |
| [Bugzilla](bugzilla.md) | Bugzilla issue tracker |
@@ -48,7 +48,7 @@ Click on the service links to see further configuration instructions and details
| Pipelines emails | Email the pipeline status to a list of recipients |
| [Slack Notifications](slack.md) | Send GitLab events (e.g. issue created) to Slack as notifications |
| [Slack slash commands](slack_slash_commands.md) **(CORE ONLY)** | Use slash commands in Slack to control GitLab |
-| [GitLab Slack application](gitlab_slack_application.md) **[FREE ONLY]** | Use Slack's official application |
+| [GitLab Slack application](gitlab_slack_application.md) **(FREE ONLY)** | Use Slack's official application |
| PivotalTracker | Project Management Software (Source Commits Endpoint) |
| [Prometheus](prometheus.md) | Monitor the performance of your deployed apps |
| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 01b6650bfab..61c30e0b0ef 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -121,6 +121,151 @@ GitLab supports a limited set of [CI variables](../../../ci/variables/README.htm
To specify a variable in a query, enclose it in curly braces with a leading percent. For example: `%{ci_environment_slug}`.
+### Defining custom dashboards per project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/59974) in GitLab 12.1.
+
+By default, all projects include a GitLab-defined Prometheus dashboard, which
+includes a few key metrics, but you can also define your own custom dashboards.
+
+NOTE: **Note:**
+The custom metrics as defined below do not support alerts, unlike
+[additional metrics](#adding-additional-metrics-premium).
+
+Dashboards have several components:
+
+- Panel groups, which comprise panels.
+- Panels, which support one or more metrics.
+
+To configure a custom dashboard:
+
+1. Create a YAML file with the `.yml` extension under your repository's root
+ directory inside `.gitlab/dashboards/`. For example, create
+ `.gitlab/dashboards/prom_alerts.yml` with the following contents:
+
+ ```yaml
+ dashboard: 'Dashboard Title'
+ panel_groups:
+ - group: 'Group Title'
+ panels:
+ - type: area-chart
+ title: "Chart Title"
+ y_label: "Y-Axis"
+ metrics:
+ - id: metric_of_ages
+ query_range: 'http_requests_total'
+ label: "Metric of Ages"
+ unit: "count"
+ ```
+
+ The above sample dashboard would display a single area chart. Each file should
+ define the layout of the dashboard and the Prometheus queries used to populate
+ data.
+
+1. Save the file, commit, and push to your repository.
+1. Navigate to your project's **Operations > Metrics** and choose the custom
+ dashboard from the dropdown.
+
+NOTE: **Note:**
+Configuration files nested under subdirectories of `.gitlab/dashboards` are not
+supported and will not be available in the UI.
+
+The following tables outline the details of expected properties.
+
+**Dashboard properties:**
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| `dashboard` | string | yes | Heading for the dashboard. Only one dashboard should be defined per file. |
+| `panel_groups` | array | yes | The panel groups which should be on the dashboard. |
+
+**Panel group (`panel_groups`) properties:**
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| `group` | string | required | Heading for the panel group. |
+| `priority` | number | optional, defaults to order in file | Order to appear on the dashboard. Higher number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
+| `panels` | array | required | The panels which should be in the panel group. |
+
+**Panel (`panels`) properties:**
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------- |
+| `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. |
+| `metrics` | array | yes | The metrics which should be displayed in the panel. |
+
+**Metrics (`metrics`) properties:**
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| `id` | string | no | Used for associating dashboard metrics with database records. Must be unique across dashboard configuration files. Required for [alerting](#setting-up-alerts-for-prometheus-metrics-ultimate) (support not yet enabled, see [relevant issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60319)). |
+| `unit` | string | yes | Defines the unit of the query's return data. |
+| `label` | string | no, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. |
+| `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)
+
### Setting up alerts for Prometheus metrics **(ULTIMATE)**
#### Managed Prometheus instances
@@ -169,7 +314,17 @@ Alerts can be used to trigger actions, like open an issue automatically (enabled
1. Optionally, select whether to send an email notification to the developers of the project.
1. Click **Save changes**.
-Once enabled, an issue will be opened automatically when an alert is triggered. The author of the issue will be the GitLab Alert Bot. To further customize the issue, you can add labels, mentions, or any other supported [quick action](../quick_actions.md) in the selected issue template.
+Once enabled, an issue will be opened automatically when an alert is triggered which contains values extracted from [alert's payload](https://prometheus.io/docs/alerting/configuration/#webhook_config
+):
+
+- Issue author: `GitLab Alert Bot`
+- Issue title: Extract from `annotations/title`, `annotations/summary` or `labels/alertname`
+- Alert `Summary`: A list of properties
+ - `starts_at`: Alert start time via `startsAt`
+ - `full_query`: Alert query extracted from `generatorURL`
+ - Optional list of attached annotations extracted from `annotations/*`
+
+To further customize the issue, you can add labels, mentions, or any other supported [quick action](../quick_actions.md) in the selected issue template.
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.
diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md
index 0d300d3c418..9e17edf7d36 100644
--- a/doc/user/project/integrations/prometheus_library/kubernetes.md
+++ b/doc/user/project/integrations/prometheus_library/kubernetes.md
@@ -13,15 +13,15 @@ integration services must be enabled.
- Average Memory Usage (MB):
- ```
- 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
- ```
+ ```
+ 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
+ ```
- Average CPU Utilization (%):
- ```
- 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))
- ```
+ ```
+ 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))
+ ```
## Configuring Prometheus to monitor for Kubernetes metrics
@@ -48,12 +48,12 @@ These metrics expect the [Deployment](https://kubernetes.io/docs/concepts/worklo
- Average Memory Usage (MB)
- ```
- 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
- ```
+ ```
+ 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
+ ```
- Average CPU Utilization (%)
- ```
- 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))
- ```
+ ```
+ 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))
+ ```
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/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md
index 93dc2a2e4ca..ec9eb7b62bb 100644
--- a/doc/user/project/issues/crosslinking_issues.md
+++ b/doc/user/project/issues/crosslinking_issues.md
@@ -47,7 +47,7 @@ display in both issues. The same is valid when mentioning issues in [merge reque
## From Merge Requests
Mentioning issues in merge request comments works exactly the same way as
-they do for [related issues](#from-related-issues).
+they do for [related issues](#from-related-issues).
When you mention an issue in a merge request description, it will simply
[link the issue and merge request together](#from-related-issues). Additionally,
diff --git a/doc/user/project/issues/csv_export.md b/doc/user/project/issues/csv_export.md
index 0b7a7af5927..d3f53c09fb3 100644
--- a/doc/user/project/issues/csv_export.md
+++ b/doc/user/project/issues/csv_export.md
@@ -48,7 +48,6 @@ Exported issues are always sorted by `Issue ID`.
Data will be encoded with a comma as the column delimiter, with `"` used to quote fields if needed, and newlines to separate rows. The first row will be the headers, which are listed in the following table along with a description of the values:
-
| Column | Description |
|---------|-------------|
| Issue ID | Issue `iid` |
@@ -71,7 +70,6 @@ Data will be encoded with a comma as the column delimiter, with `"` used to quot
| Time Estimate | [Time estimate](../../../workflow/time_tracking.md#estimates) in seconds |
| Time Spent | [Time spent](../../../workflow/time_tracking.md#time-spent) in seconds |
-
## 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.
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..7c24226a6c4
--- /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..d6d391c6dd9
--- /dev/null
+++ b/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png
Binary files differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index e917697e973..6706e1c2e2b 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
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index 9c4d0de46d3..7b031f83cb1 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -116,13 +116,13 @@ issue boards or the issue list.
#### 11. Lock issue
-You can [lock the discussions](../../discussions/index.md#lock-discussions) in the issue,
+You can [lock the threads](../../discussions/index.md#lock-discussions) in the issue,
to prevent further comments from being added.
#### 12. Participants
-All the users involved in that issue. Either they participated in the [discussion](../../discussions/index.md),
-or were mentioned in the description or discussions.
+All the users involved in that issue. Either they participated in the [thread](../../discussions/index.md),
+or were mentioned in the description or threads.
#### 13. Notifications
@@ -187,8 +187,8 @@ You can also click the `+` to add more related issues.
#### 19. Related Merge Requests
-Merge requests that were mentioned in that issue's description or in the issue discussion
-thread are listed as [related merge requests](crosslinking_issues.md#from-merge-requests) here.
+Merge requests that were mentioned in that issue's description or in the issue thread
+are listed as [related merge requests](crosslinking_issues.md#from-merge-requests) here.
Also, if the current issue was mentioned as related in another merge request, that
merge request will be listed here.
@@ -200,17 +200,17 @@ dropdown list of available [GitLab Flavored Markdown Emoji](../../markdown.md#em
TIP: **Tip:**
Posting "+1" as a comment in a thread spams all subscribed participants of that issue,
-clutters the discussion threads, and is not recommended. Awarding an emoji is a way
+clutters the threads, and is not recommended. Awarding an emoji is a way
to let them know your reaction without spamming them.
#### 21. Show all activity
You can filter what is displayed in the issue history by clicking on **Show all activity**
-and selecting either **Show comments only**, which only shows discussions and hides
-updates to the issue, or **Show history only**, which hides discussions and only shows updates.
+and selecting either **Show comments only**, which only shows threads and hides
+updates to the issue, or **Show history only**, which hides threads and only shows updates.
- You can mention a user or a group present in your GitLab instance with
- `@username` or `@groupname` and they will be notified via To-Do items
+ `@username` or `@groupname` and they will be notified via To-Do items
and email, unless they have [disabled all notifications](#13-notifications)
in their profile settings.
- Mentions for yourself (the current logged in user), will be highlighted
@@ -242,15 +242,15 @@ filtered, as shown above.
Collaborate in the issue by posting comments in its thread. This text field also fully
supports [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
-#### 25. Submit Comment, start a discussion, or comment and close
+#### 25. Submit Comment, start a thread, or comment and close
Once you write a comment, you can:
- Click **Comment** and your comment will be published.
-- Choose **Start discussion** from the dropdown list and start a new [discussion thread](../../discussions/index.md#threaded-discussions)
+- Choose **Start thread** from the dropdown list and start a new [thread](../../discussions/index.md#threaded-discussions)
within that issue's main thread to discuss specific points. This invites other participants
- to reply directly to your discussion, keeping related comments grouped together.
+ to reply directly to your thread, keeping related comments grouped together.
-![Comment or discussion](img/comment-or-discussion.png)
+![Comment or thread](img/comment-or-discussion.png)
You can also close the issue from here, so you don't need to scroll to the top of the issue page.
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/sorting_issue_lists.md b/doc/user/project/issues/sorting_issue_lists.md
new file mode 100644
index 00000000000..ba120783430
--- /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.1.
+
+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/members/index.md b/doc/user/project/members/index.md
index 2a490e3fd7f..e343fd45488 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -10,8 +10,6 @@ or import a new user to your project.
To view, edit, add, and remove project's members, go to your
project's **Settings > Members**.
----
-
## Add a user
Right next to **People**, start typing the name or username of the user you
@@ -19,22 +17,16 @@ want to add.
![Search for people](img/add_user_search_people.png)
----
-
Select the user and the [permission level](../../permissions.md)
that you'd like to give the user. Note that you can select more than one user.
![Give user permissions](img/add_user_give_permissions.png)
----
-
Once done, hit **Add users to project** and they will be immediately added to
your project with the permissions you gave them above.
![List members](img/add_user_list_members.png)
----
-
From there on, you can either remove an existing user or change their access
level to the project.
@@ -47,8 +39,6 @@ In the dropdown menu, you can see only the projects you are Maintainer on.
![Import members from another project](img/add_user_import_members_from_another_project.png)
----
-
Select the one you want and hit **Import project members**. A flash message
notifying you that the import was successful will appear, and the new members
are now in the project's members list. Notice that the permissions that they
@@ -56,8 +46,6 @@ had on the project you imported from are retained.
![Members list of new members](img/add_user_imported_members.png)
----
-
## Invite people using their e-mail address
If a user you want to give access to doesn't have an account on your GitLab
@@ -66,23 +54,17 @@ user search field.
![Invite user by mail](img/add_user_email_search.png)
----
-
As you can imagine, you can mix inviting multiple people and adding existing
GitLab users to the project.
![Invite user by mail ready to submit](img/add_user_email_ready.png)
----
-
Once done, hit **Add users to project** and watch that there is a new member
with the e-mail address we used above. From there on, you can resend the
invitation, change their access level, or even delete them.
![Invite user members list](img/add_user_email_accept.png)
----
-
Once the user accepts the invitation, they will be prompted to create a new
GitLab account using the same e-mail address the invitation was sent to.
@@ -97,15 +79,11 @@ side of your screen.
![Request access button](img/request_access_button.png)
----
-
Project owners & maintainers will be notified of your request and will be able to approve or
decline it on the members page.
![Manage access requests](img/access_requests_management.png)
----
-
If you change your mind before your request is approved, just click the
**Withdraw Access Request** button.
diff --git a/doc/user/project/merge_requests/blocking_merge_requests.md b/doc/user/project/merge_requests/blocking_merge_requests.md
new file mode 100644
index 00000000000..0506a7cb4a5
--- /dev/null
+++ b/doc/user/project/merge_requests/blocking_merge_requests.md
@@ -0,0 +1,133 @@
+---
+type: reference, concepts
+---
+
+# Blocking merge requests **(PREMIUM)**
+
+> Introduced in GitLab Premium 12.2
+
+Blocking merge requests allow dependencies between MRs to be expressed. If a
+merge request is blocked by another MR, it cannot be merged until that blocking
+MR is itself merged.
+
+NOTE: **Note:**
+Blocking merge requests are a **PREMIUM** feature, but this restriction is only
+enforced for the blocked merge request. A merge request in a **CORE** or
+**STARTER** project can block a **PREMIUM** merge request, but not vice-versa.
+
+## 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 MR
+ implementing the feature to be documented
+* Require an MR updating a permissions matrix to be merged before merging an
+ MR from someone who hasn't yet been granted permissions
+
+It is common for a single logical change to span several merge requests. These
+MRs may all be in a single project, or they may be 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` MR before the `awesome-lib` one would break the `master`
+branch.
+
+The `awesome-project` MR 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` MR to be manually tracked, and doesn't
+scale well if the `awesome-project` MR depends on changes to **several** other
+projects.
+
+By marking the `awesome-project` MR as blocked on the `awesome-lib` MR instead,
+the status of the dependency is automatically tracked by GitLab, and the WIP
+state can be used to communicate the readiness of the code in each individual
+MR instead.
+
+## Configuration
+
+To continue the above example, you can configure a block when creating the
+new MR in `awesome-project` (or by editing it, if it already exists). The block
+needs to be configured on the MR that will be **blocked**, rather than on the
+**blocking** MR. There is a "Blocking merge requests" section in the form:
+
+![Blocking merge requests form control](img/edit_blocking_merge_requests.png)
+
+Anyone who can edit a merge request can change the list of blocking merge
+requests.
+
+New blocks can be added by reference, by URL, or by using autcompletion. To
+remove a block, press the "X" by its reference.
+
+As blocks can be specified across projects, it's possible that someone else has
+added a block for a merge request in a project you don't have access to. These
+are shown as a simple count:
+
+![Blocking merge requests form control with inaccessible MRs](img/edit_blocking_merge_requests_inaccessible.png)
+
+If necessary, you can remove all the blocks like this by pressing the "X", just
+as you would for a single, visible block.
+
+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 blocks, and the status of each one, is shown in the merge
+request widget:
+
+![Blocking merge requests in merge request widget](img/show_blocking_merge_requests_in_mr_widget.png)
+
+Until all blocking merge requests have, themselves, been merged, the "Merge"
+button will be disabled. In particular, note that **closed** merge requests
+still block their dependents - it is impossible to automatically determine if
+merge requests that were blocked by that MR when it was open, are still blocked
+when it is closed.
+
+If a merge request has been closed **and** the block is no longer relevant, it
+must be removed as a blocking MR, following the instructions above, before
+merge.
+
+## Limitations
+
+* API support: [gitlab-ee#12551](https://gitlab.com/gitlab-org/gitlab-ee/issues/12551)
+* Blocking relationships 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. Blocking merge requests can be
+described as a graph of dependencies. The simplest possible graph has one
+merge request blocking another:
+
+```mermaid
+graph LR;
+ myfriend/awesome-lib!10-->mycorp/awesome-project!100;
+```
+
+A more complex (and still supported) graph might have several MRs blocking
+another from being merged:
+
+```mermaid
+graph LR;
+ myfriend/awesome-lib!10-->mycorp/awesome-project!100;
+ herfriend/another-lib!1-->mycorp/awesome-project!100;
+```
+
+We also support one MR blocking several others from being merged:
+
+```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` would be blocked from being merged by
+`herfriend/another-lib!1`, and would also block `mycorp/awesome-project!100`
+from being merged. This is **not** yet supported.
+
diff --git a/doc/user/project/merge_requests/img/edit_blocking_merge_requests.png b/doc/user/project/merge_requests/img/edit_blocking_merge_requests.png
new file mode 100644
index 00000000000..0fe26d602e3
--- /dev/null
+++ b/doc/user/project/merge_requests/img/edit_blocking_merge_requests.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/edit_blocking_merge_requests_inaccessible.png b/doc/user/project/merge_requests/img/edit_blocking_merge_requests_inaccessible.png
new file mode 100644
index 00000000000..a1003c41c22
--- /dev/null
+++ b/doc/user/project/merge_requests/img/edit_blocking_merge_requests_inaccessible.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/show_blocking_merge_requests_in_mr_widget.png b/doc/user/project/merge_requests/img/show_blocking_merge_requests_in_mr_widget.png
new file mode 100644
index 00000000000..a1241f9e38c
--- /dev/null
+++ b/doc/user/project/merge_requests/img/show_blocking_merge_requests_in_mr_widget.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 37a0630d0f3..d5ca853eff5 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -18,7 +18,7 @@ It is as simple as the name implies: a _request_ to _merge_ one branch into anot
With GitLab merge requests, you can:
- Compare the changes between two [branches](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell#_git_branching)
-- [Review and discuss](../../discussions/index.md#discussions) the proposed modifications inline
+- [Review and discuss](../../discussions/index.md#threads) the proposed modifications inline
- Live preview the changes when [Review Apps](../../../ci/review_apps/index.md) is configured for your project
- Build, test, and deploy your code in a per-branch basis with built-in [GitLab CI/CD](../../../ci/README.md)
- Prevent the merge request from being merged before it's ready with [WIP MRs](#work-in-progress-merge-requests)
@@ -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 [Blocking Merge Requests](#blocking-merge-requests-premium) **(PREMIUM)**
## Use cases
@@ -155,13 +156,13 @@ and remember to merge the request manually.
[Learn more about merging when pipeline succeeds.](merge_when_pipeline_succeeds.md)
-## Resolve discussion comments in merge requests reviews
+## Resolve threads in merge requests reviews
Keep track of the progress during a code review with resolving comments.
Resolving comments prevents you from forgetting to address feedback and lets
-you hide discussions that are no longer relevant.
+you hide threads that are no longer relevant.
-[Read more about resolving discussion comments in merge requests reviews.](../../discussions/index.md)
+[Read more about resolving threads in merge requests reviews.](../../discussions/index.md)
## Commenting on any file line in merge requests
@@ -192,7 +193,7 @@ commit when merging, to allow for a neater commit history.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6.
As a reviewer, you can add suggestions to change the content in
-merge request discussions, and users with appropriate [permission](../../permissions.md)
+merge request threads, and users with appropriate [permission](../../permissions.md)
can easily apply them to the codebase directly from the UI. Read
through the documentation on [Suggest changes](../../discussions/index.md#suggest-changes)
to learn more.
@@ -274,7 +275,8 @@ branch already exists, the patches will be applied on top of it.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26752) in GitLab 11.10.
NOTE: **Note:**
-Git push options are only available with Git 2.10 or newer.
+Git push options are only available with Git 2.10 or newer. With Git older than 2.18
+`git push --push-option=...` should be used instead of `git push -o ...`.
GitLab supports using
[Git push options](https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt)
@@ -284,6 +286,7 @@ 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.
### Create a new merge request using git push options
@@ -327,6 +330,19 @@ 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
+
+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.
+
## Find the merge request that introduced a change
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2383) in GitLab 10.5.
@@ -364,6 +380,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].
@@ -385,6 +406,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.
@@ -395,6 +427,21 @@ GitLab runs the [Sitespeed.io container][sitespeed-container] and displays the d
[Read more about Browser Performance Testing.](browser_performance_testing.md)
+## Blocking Merge Requests **(PREMIUM)**
+
+> Introduced in [GitLab Premium][products] 12.2.
+
+A single logical change may be split across several merge requests, and perhaps
+even across several projects. When this happens, the order in which MRs are
+merged is important.
+
+GitLab allows you to specify that a merge request is blocked by other MRs. With
+this relationship in place, the merge request cannot be merged until all of its
+blockers have also been merged, helping to maintain the consistency of a single
+logical change.
+
+[Read more about Blocking Merge Requests.](blocking_merge_requests.md)
+
## Security reports **(ULTIMATE)**
GitLab can scan and report any vulnerabilities found in your project.
diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index 0f392676316..220795d6f15 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -162,7 +162,7 @@ the merge request is unblocked and can be merged.
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-discussions-are-resolved)
+[pending discussions](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved)
or a [failed CI/CD pipeline](merge_when_pipeline_succeeds.md).
## Code Owners approvals **(PREMIUM)**
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index 0dd60d84c42..50a4e514d49 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -36,7 +36,7 @@ changes to be reviewed.
## Only allow merge requests to be merged if the pipeline succeeds
You can prevent merge requests from being merged if their pipeline did not succeed
-or if there are discussions to be resolved.
+or if there are threads to be resolved.
Navigate to your project's settings page and expand the **Merge requests** section.
In the **Merge checks** subsection, select the **Pipelines must succeed** check
diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
index 142178ba241..ea59644fce6 100644
--- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -5,7 +5,7 @@ type: reference, concepts
# "Work In Progress" merge requests
If a merge request is not yet ready to be merged, perhaps due to continued development
-or open discussions, you can prevent it from being accepted before it's ready by flagging
+or open threads, you can prevent it from being accepted before it's ready by flagging
it as a **Work In Progress**. This will disable the "Merge" button, preventing it from
being merged, and it will stay disabled until the "WIP" flag has been removed.
@@ -19,7 +19,7 @@ There are several ways to flag a merge request as a Work In Progress:
**Start the title with WIP:**, under the title box, when editing the merge request's
description will have the same effect.
- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-and-merge-requests)
- in a discussion comment in the merge request. This is a toggle, and can be repeated
+ in a comment in the merge request. This is a toggle, and can be repeated
to change the status back. Note that any other text in the comment will be discarded.
- Add "wip" or "WIP" to the start of a commit message targeting the merge request's
source branch. This is not a toggle, and doing it again in another commit will have
@@ -34,7 +34,7 @@ Similar to above, when a Merge Request is ready to be merged, you can remove the
**Remove the WIP: prefix from the title**, under the title box, when editing the merge
request's description, will have the same effect.
- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-and-merge-requests)
- in a discussion comment in the merge request. This is a toggle, and can be repeated
+ in a comment in the merge request. This is a toggle, and can be repeated
to change the status back. Note that any other text in the comment will be discarded.
- Click on the **Resolve WIP status** button near the bottom of the merge request description,
next to the "Merge" button (see [image above](#work-in-progress-merge-requests)).
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index b0745b1e2c5..142462e1879 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -14,12 +14,12 @@ the start and end of your Agile sprint.
Set the milestone title to the name of your Agile sprint,
such as `November 2018 sprint`.
Add an issue to your Agile sprint by associating
-the milestone to the issue.
+the milestone to the issue.
## Milestones as releases
Milestones can be used as releases.
-Set the milestone due date to represent the release date of your release.
+Set the milestone due date to represent the release date of your release.
(And leave the milestone start date blank.)
Set the milestone title to the version of your release,
such as `Version 9.4`.
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index d35a8568394..494dd539da7 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -6,8 +6,6 @@ GitLab 8.12 has a completely redesigned [job permissions] system. You can find
all discussion and all our concerns when choosing the current approach in issue
[#18994](https://gitlab.com/gitlab-org/gitlab-ce/issues/18994).
----
-
Jobs permissions should be tightly integrated with the permissions of a user
who is triggering a job.
@@ -108,8 +106,6 @@ and to checkout project sources.
It could also be used with the GitLab Container Registry for that project,
allowing pulling and pushing Docker images from within the CI job.
----
-
GitLab would create a special checkout URL like:
```
diff --git a/doc/user/project/packages/maven_repository.md b/doc/user/project/packages/maven_repository.md
index 27c052fb2bc..c61402afb2c 100644
--- a/doc/user/project/packages/maven_repository.md
+++ b/doc/user/project/packages/maven_repository.md
@@ -198,7 +198,7 @@ domain name.
NOTE: **Note:**
For retrieving artifacts, you can use either the
[URL encoded](../../../api/README.md#namespaced-path-encoding) path of the group
-(e.g., `group%2Fsubgroup`) or the group's ID (e.g., `12`).
+(e.g., `group%2Fsubgroup`) or the group's ID (e.g., `12`).
### Instance level Maven endpoint
@@ -279,59 +279,59 @@ shows how to create a new package each time the `master` branch is updated:
Add the server section with the same id you defined in your `pom.xml` file.
For example, in our case it's `gitlab-maven`:
- ```xml
- <settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
- <servers>
- <server>
- <id>gitlab-maven</id>
- <configuration>
- <httpHeaders>
- <property>
- <name>Job-Token</name>
- <value>${env.CI_JOB_TOKEN}</value>
- </property>
- </httpHeaders>
- </configuration>
- </server>
- </servers>
- </settings>
- ```
+ ```xml
+ <settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd">
+ <servers>
+ <server>
+ <id>gitlab-maven</id>
+ <configuration>
+ <httpHeaders>
+ <property>
+ <name>Job-Token</name>
+ <value>${env.CI_JOB_TOKEN}</value>
+ </property>
+ </httpHeaders>
+ </configuration>
+ </server>
+ </servers>
+ </settings>
+ ```
1. Make sure your `pom.xml` file includes the following:
- ```xml
- <repositories>
- <repository>
- <id>gitlab-maven</id>
- <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
- </repository>
- </repositories>
- <distributionManagement>
- <repository>
- <id>gitlab-maven</id>
- <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
- </repository>
- <snapshotRepository>
- <id>gitlab-maven</id>
- <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
- </snapshotRepository>
- </distributionManagement>
- ```
-
- TIP: **Tip:**
- You can either let Maven utilize the CI environment variables or hardcode your project's ID.
+ ```xml
+ <repositories>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ </repository>
+ </repositories>
+ <distributionManagement>
+ <repository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ </repository>
+ <snapshotRepository>
+ <id>gitlab-maven</id>
+ <url>https://gitlab.com/api/v4/projects/${env.CI_PROJECT_ID}/packages/maven</url>
+ </snapshotRepository>
+ </distributionManagement>
+ ```
+
+ TIP: **Tip:**
+ You can either let Maven utilize the CI environment variables or hardcode your project's ID.
1. Add a `deploy` job to your `.gitlab-ci.yml` file:
- ```yaml
- deploy:
- image: maven:3.3.9-jdk-8
- script:
- - 'mvn deploy -s ci_settings.xml'
- only:
- - master
- ```
+ ```yaml
+ deploy:
+ image: maven:3.3.9-jdk-8
+ script:
+ - 'mvn deploy -s ci_settings.xml'
+ only:
+ - master
+ ```
1. Push those files to your repository.
diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md
index b2cfe10836f..ca0aa9965ef 100644
--- a/doc/user/project/packages/npm_registry.md
+++ b/doc/user/project/packages/npm_registry.md
@@ -11,11 +11,6 @@ project can have its own space to store NPM packages.
NOTE: **Note:**
Only [scoped](https://docs.npmjs.com/misc/scope) packages are supported.
-
-NOTE: **Note:**
-As `@group/subgroup/project` is not a valid NPM package name, publishing a package
-within a subgroup is not supported yet.
-
## Enabling the NPM Registry
NOTE: **Note:**
@@ -36,12 +31,15 @@ get familiar with the package naming convention.
## Package naming convention
-**Only packages that have the same path as the project** are supported. For
- example:
+**Packages must be scoped in the root namespace of the project**. The package
+name may be anything but it is preferred that the project name be used unless
+it is not possible due to a naming collision. For example:
| Project | Package | Supported |
| ---------------------- | ----------------------- | --------- |
| `foo/bar` | `@foo/bar` | Yes |
+| `foo/bar/baz` | `@foo/baz` | Yes |
+| `foo/bar/buz` | `@foo/anything` | Yes |
| `gitlab-org/gitlab-ce` | `@gitlab-org/gitlab-ce` | Yes |
| `gitlab-org/gitlab-ce` | `@foo/bar` | No |
@@ -51,35 +49,32 @@ 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.
@@ -113,6 +108,9 @@ npm publish
You can then navigate to your project's **Packages** page and see the uploaded
packages or even delete them.
+If you attempt to publish a package with a name that already exists within
+a given scope, you will receive a `403 Forbidden!` error.
+
## Uploading a package with the same version twice
If you upload a package with a same name and version twice, GitLab will show
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
new file mode 100644
index 00000000000..3b80370d4a9
--- /dev/null
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/dns_concepts.md
@@ -0,0 +1,95 @@
+---
+type: concepts
+---
+
+# DNS records overview
+
+_Read this document for a brief overview of DNS records in the scope
+of GitLab Pages, for beginners in web development._
+
+A Domain Name System (DNS) web service routes visitors to websites
+by translating domain names (such as `www.example.com`) into the
+numeric IP addresses (such as `192.0.2.1`) that computers use to
+connect to each other.
+
+A DNS record is created to point a (sub)domain to a certain location,
+which can be an IP address or another domain. In case you want to use
+GitLab Pages with your own (sub)domain, you need to access your domain's
+registrar control panel to add a DNS record pointing it back to your
+GitLab Pages site.
+
+Note that **how to** add DNS records depends on which server your domain
+is hosted on. Every control panel has its own place to do it. If you are
+not an admin of your domain, and don't have access to your registrar,
+you'll need to ask for the technical support of your hosting service
+to do it for you.
+
+To help you out, we've gathered some instructions on how to do that
+for the most popular hosting services:
+
+- [Amazon](http://docs.aws.amazon.com/gettingstarted/latest/swh/getting-started-configure-route53.html)
+- [Bluehost](https://my.bluehost.com/cgi/help/559)
+- [CloudFlare](https://support.cloudflare.com/hc/en-us/articles/200169096-How-do-I-add-A-records-)
+- [cPanel](https://documentation.cpanel.net/display/ALD/Edit+DNS+Zone)
+- [DreamHost](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-)
+- [Go Daddy](https://www.godaddy.com/help/add-an-a-record-19238)
+- [Hostgator](http://support.hostgator.com/articles/changing-dns-records)
+- [Inmotion hosting](https://my.bluehost.com/cgi/help/559)
+- [Media Temple](https://mediatemple.net/community/products/dv/204403794/how-can-i-change-the-dns-records-for-my-domain)
+- [Microsoft](https://msdn.microsoft.com/en-us/library/bb727018.aspx)
+
+If your hosting service is not listed above, you can just try to
+search the web for `how to add dns record on <my hosting service>`.
+
+## `A` record
+
+A DNS A record maps a host to an IPv4 IP address.
+It points a root domain as `example.com` to the host's IP address as
+`192.192.192.192`.
+
+Example:
+
+- `example.com` => `A` => `192.192.192.192`
+
+## CNAME record
+
+CNAME records define an alias for canonical name for your server (one defined
+by an A record). It points a subdomain to another domain.
+
+Example:
+
+- `www` => `CNAME` => `example.com`
+
+This way, visitors visiting `www.example.com` will be redirected to
+`example.com`.
+
+## MX record
+
+MX records are used to define the mail exchanges that are used for the domain.
+This helps email messages arrive at your mail server correctly.
+
+Example:
+
+- `MX` => `mail.example.com`
+
+Then you can register emails for `users@mail.example.com`.
+
+## TXT record
+
+A `TXT` record can associate arbitrary text with a host or other name. A common
+use is for site verification.
+
+Example:
+
+- `example.com`=> TXT => `"google-site-verification=6P08Ow5E-8Q0m6vQ7FMAqAYIDprkVV8fUf_7hZ4Qvc8"`
+
+This way, you can verify the ownership for that domain name.
+
+## All combined
+
+You can have one DNS record or more than one combined:
+
+- `example.com` => `A` => `192.192.192.192`
+- `www` => `CNAME` => `example.com`
+- `MX` => `mail.example.com`
+- `example.com`=> TXT => `"google-site-verification=6P08Ow5E-8Q0m6vQ7FMAqAYIDprkVV8fUf_7hZ4Qvc8"` \ No newline at end of file
diff --git a/doc/user/project/pages/img/add_certificate_to_pages.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/add_certificate_to_pages.png
index d92a981dc60..d92a981dc60 100644
--- a/doc/user/project/pages/img/add_certificate_to_pages.png
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/add_certificate_to_pages.png
Binary files differ
diff --git a/doc/user/project/pages/img/dns_add_new_a_record_example_updated_2018.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/dns_add_new_a_record_example_updated_2018.png
index 0150329d4b2..0150329d4b2 100644
--- a/doc/user/project/pages/img/dns_add_new_a_record_example_updated_2018.png
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/dns_add_new_a_record_example_updated_2018.png
Binary files differ
diff --git a/doc/user/project/pages/img/dns_cname_record_example.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/dns_cname_record_example.png
index 43d1a838544..43d1a838544 100644
--- a/doc/user/project/pages/img/dns_cname_record_example.png
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/dns_cname_record_example.png
Binary files differ
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
new file mode 100644
index 00000000000..b4dcdc9bb60
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..2e825e84d92
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..db8f25bc6c3
--- /dev/null
+++ 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
new file mode 100644
index 00000000000..54ecc42d2b9
--- /dev/null
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
@@ -0,0 +1,277 @@
+---
+last_updated: 2019-07-04
+type: reference, howto
+redirect_from: 'https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html'
+---
+
+# Custom domains and SSL/TLS Certificates
+
+Setting up GitLab Pages with custom domains, and adding SSL/TLS certificates to them, are optional features of GitLab Pages.
+
+To use one or more custom domain names with your Pages site, you can:
+
+- Add a [custom **root domain** or a **subdomain**](#set-up-pages-with-a-custom-domain).
+- Add [SSL/TLS certification](#adding-an-ssltls-certificate-to-pages).
+
+## Set up Pages with a custom domain
+
+To set up Pages with a custom domain name, read the requirements
+and steps below.
+
+### Requirements
+
+- A GitLab Pages website up and running, served under the default Pages domain
+ (`*.gitlab.io`, for GitLab.com).
+- A custom domain name `example.com` or subdomain `subdomain.example.com`.
+- Access to your domain's server control panel to set up DNS records:
+ - A DNS A or CNAME record poiting your domain to GitLab Pages server.
+ - A DNS TXT record to verify your domain's ownership.
+
+### Steps
+
+Follow the steps below to add your custom domain to Pages. See also
+this document for an [overview on DNS records](dns_concepts.md).
+
+#### 1. Add a custom domain to Pages
+
+Navigate to your project's **Setting > Pages** and click **+ New domain**
+to add your custom domain to GitLab Pages. You can choose whether to:
+
+- Add an [SSL/TLS certificate](#adding-an-ssltls-certificate-to-pages).
+- Leave it blank (it can be added later).
+
+Click **Create New Domain**.
+
+![Add new domain](img/add_certificate_to_pages.png)
+
+#### 2. Get the verification code
+
+Once you have added a new domain to Pages, the verification code will be prompted to you. Copy the values from GitLab and paste them in your domain's control panel as a TXT record on the next step.
+
+![Get the verification code](img/get_domain_verification_code_v12_0.png)
+
+#### 3. Set up DNS records for Pages
+
+Read this document for an [overview of DNS records for Pages](dns_concepts.md).
+If you're familiar with the subject, follow the instructions below
+according to the type of domain you want to use with your Pages site:
+
+- [For root domains](#for-root-domains), `example.com`.
+- [For subdomains](#for-subdomains), `subdomain.example.com`.
+- [For both](#for-both-root-and-subdomains).
+
+##### For root domains
+
+Root domains (`example.com`) require:
+
+- A [DNS A record](dns_concepts.md#a-record) pointing your domain to the Pages server.
+- A [TXT record](dns_concepts.md#txt-record) to verify your domain's ownership.
+
+| From | DNS Record | To |
+| ---- | ---------- | -- |
+| example.com | A | 35.185.44.232 |
+| _gitlab-pages-verification-code.example.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff |
+
+For projects on GitLab.com, this IP is `35.185.44.232`.
+For projects living in other GitLab instances (CE or EE), please contact
+your sysadmin asking for this information (which IP address is Pages
+server running on your instance).
+
+![DNS A record pointing to GitLab.com Pages server](img/dns_add_new_a_record_example_updated_2018.png)
+
+CAUTION: **Caution:**
+Note that if you use your root domain for your GitLab Pages website
+**only**, and if your domain registrar supports this feature, you can
+add a DNS apex `CNAME` record instead of an `A` record. The main
+advantage of doing so is that when GitLab Pages IP on GitLab.com
+changes for whatever reason, you don't need to update your `A` record.
+There may be a few exceptions, but **this method is not recommended**
+as it most likely won't work if you set an [`MX` record](dns_concepts.md#mx-record) for your root domain.
+
+##### For subdomains
+
+Subdomains (`subdomain.example.com`) require:
+
+- A DNS [CNAME record](dns_concepts.md#cname-record) record pointing your subdomain to the Pages server.
+- A DNS [TXT record](dns_concepts.md#txt-record) to verify your domain's ownership.
+
+| From | DNS Record | To |
+| ---- | ---------- | -- |
+| subdomain.example.com | CNAME | namespace.gitlab.io |
+| _gitlab-pages-verification-code.subdomain.example.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff |
+
+Note that, whether it's a user or a project website, the `CNAME`
+should point to your Pages domain (`namespace.gitlab.io`),
+without any `/project-name`.
+
+![DNS CNAME record pointing to GitLab.com project](img/dns_cname_record_example.png)
+
+##### For both root and subdomains
+
+There are a few cases where you need point both subdomain and root
+domain to the same website, for instance, `example.com` and `www.example.com`.
+
+They require:
+
+- A DNS A record for the domain.
+- A DNS CNAME record for the subdomain.
+- A DNS TXT record for each.
+
+| From | DNS Record | To |
+| ---- | ---------- | -- |
+| example.com | A | 35.185.44.232 |
+| _gitlab-pages-verification-code.example.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff |
+|---+---|
+| www.example.com | CNAME | namespace.gitlab.io |
+| _gitlab-pages-verification-code.www.example.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff |
+
+If you're using CloudFlare, check
+[Redirecting `www.domain.com` to `domain.com` with Cloudflare](#redirecting-wwwdomaincom-to-domaincom-with-cloudflare).
+
+> **Notes**:
+>
+> - **Do not** use a CNAME record if you want to point your
+ `domain.com` to your GitLab Pages site. Use an `A` record instead.
+> - **Do not** add any special chars after the default Pages
+ domain. E.g., don't point `subdomain.domain.com` to
+ or `namespace.gitlab.io/`. Some domain hosting providers may request a trailling dot (`namespace.gitlab.io.`), though.
+> - GitLab Pages IP on GitLab.com [was changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) in 2017.
+> - GitLab Pages IP on GitLab.com [has changed](https://about.gitlab.com/2018/07/19/gcp-move-update/#gitlab-pages-and-custom-domains)
+ from `52.167.214.135` to `35.185.44.232` in 2018.
+
+#### 4. Verify the domain's ownership
+
+Once you have added all the DNS records:
+
+1. Go back at your project's **Settings > Pages**.
+1. Locate your domain name and click **Details**.
+1. Click the **Retry verification** button to activate your new domain.
+
+![Verify your domain](img/retry_domain_verification_v12_0.png)
+
+As soon as your domain becomes active, your website will be available
+through your domain name.
+
+CAUTION: **Caution:**
+Considering GitLab instances with domain verification enabled,
+if the domain cannot be verified for 7 days, it will be removed
+from the GitLab project.
+
+> **Notes:**
+>
+> - Domain verification is **required for GitLab.com users**;
+ for GitLab self-managed instances, your GitLab administrator has the option
+ to [disabled custom domain verification](../../../../administration/pages/index.md#custom-domain-verification).
+> - [DNS propagation may take some time (up to 24h)](http://www.inmotionhosting.com/support/domain-names/dns-nameserver-changes/domain-names-dns-changes),
+ although it's usually a matter of minutes to complete. Until it does, verification
+ will fail and attempts to visit your domain will respond with a 404.
+> - Once your domain has been verified, leave the verification record
+ in place: your domain will be periodically reverified, and may be
+ disabled if the record is removed.
+
+### Adding more domain aliases
+
+You can add more than one alias (custom domains and subdomains) to the same project.
+An alias can be understood as having many doors leading to the same room.
+
+All the aliases you've set to your site will be listed on **Setting > Pages**.
+From that page, you can view, add, and remove them.
+
+### Redirecting `www.domain.com` to `domain.com` with Cloudflare
+
+If you use Cloudflare, you can redirect `www` to `domain.com`
+without adding both `www.domain.com` and `domain.com` to GitLab.
+
+To do so, you can use Cloudflare's page rules associated to a
+CNAME record to redirect `www.domain.com` to `domain.com`. You
+can use the following setup:
+
+1. In Cloudflare, create a DNS `A` record pointing `domain.com` to `35.185.44.232`.
+1. In GitLab, add the domain to GitLab Pages and get the verification code.
+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`.
+
+## Adding an SSL/TLS certificate to Pages
+
+Read this document for an [overview on SSL/TLS certification](ssl_tls_concepts.md).
+
+To secure your custom domain with GitLab Pages you can opt by:
+
+- Using the [Let's Encrypt integration with GitLab Pages](lets_encrypt_integration.md),
+ which automatically obtains and renews SSL certificates
+ for your Pages domains.
+- Manually adding SSL/TLS certificates to GitLab Pages websites
+ by following the steps below.
+
+### Requirements
+
+- A GitLab Pages website up and running accessible via a custom domain.
+- **A PEM certificate**: it is the certificate generated by the CA,
+ which needs to be added to the field **Certificate (PEM)**.
+- **An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority)**: (aka "root certificate"), it is
+ the part of the encryption keychain that identifies the CA.
+ Usually it's combined with the PEM certificate, but there are
+ some cases in which you need to add them manually.
+ [CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
+ are one of these cases.
+- **A private key**, it's an encrypted key which validates
+ your PEM against your domain.
+
+### Steps
+
+- To add the certificate at the time you add a new domain, go to your
+ project's **Settings > Pages > New Domain**, add the domain name and the certificate.
+- To add the certificate to a domain previously added, go to your
+ project's **Settings > Pages**, locate your domain name, click **Details** and **Edit** to add the certificate.
+
+![Pages project - adding certificates](img/add_certificate_to_pages.png)
+
+1. Add the PEM certificate to its corresponding field.
+1. If your certificate is missing its intermediate, copy
+ and paste the root certificate (usually available from your CA website)
+ and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/),
+ just jumping a line between them.
+1. Copy your private key and paste it in the last field.
+
+NOTE: **Note:**
+**Do not** open certificates or encryption keys in
+regular text editors. Always use code editors (such as
+Sublime Text, Atom, Dreamweaver, Brackets, etc).
+
+## Force HTTPS for GitLab Pages websites
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28857) in GitLab 10.7.
+
+To make your website's visitors even more secure, you can choose to
+force HTTPS for GitLab Pages. By doing so, all attempts to visit your
+website via HTTP will be automatically redirected to HTTPS via 301.
+
+It works with both GitLab's default domain and with your custom
+domain (as long as you've set a valid certificate for it).
+
+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
+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/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
new file mode 100644
index 00000000000..4588375738e
--- /dev/null
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
@@ -0,0 +1,87 @@
+---
+type: reference
+description: "Automatic Let's Encrypt SSL certificates for GitLab Pages."
+---
+
+# GitLab Pages integration with Let's Encrypt
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28996) in GitLab 12.1.
+
+The GitLab Pages integration with Let's Encrypt (LE) allows you
+to use LE certificates for your Pages website with custom domains
+without the hassle of having to issue and update them yourself;
+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:
+
+- Created a [project](../getting_started_part_two.md) in GitLab
+ containing your website's source code.
+- Acquired a domain (`example.com`) and added a [DNS entry](index.md)
+ pointing it to your Pages website.
+- [Added your domain to your Pages project](index.md#1-add-a-custom-domain-to-pages)
+ and verified your ownership.
+- Have your website up and running, accessible through your custom domain.
+
+NOTE: **Note:**
+GitLab's Let's Encrypt integration is enabled and available on GitLab.com.
+For **self-managed** GitLab instances, make sure your administrator has
+[enabled it](../../../../administration/pages/index.md#lets-encrypt-integration).
+
+## Enabling Let's Encrypt integration for your custom domain
+
+Once you've met the requirements, to enable Let's Encrypt integration:
+
+1. Navigate to your project's **Settings > Pages**.
+1. Find your domain and click **Details**.
+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)
+
+1. Click **Save changes**.
+
+Once enabled, GitLab will obtain a LE certificate and add it to the
+associated Pages domain. It will be also renewed automatically by GitLab.
+
+> **Notes:**
+>
+> - Issuing the certificate and updating Pages configuration
+> **can take up to an hour**.
+> - 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
+
+### 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
+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/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
new file mode 100644
index 00000000000..2470e78819f
--- /dev/null
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
@@ -0,0 +1,75 @@
+---
+type: concepts
+---
+
+# SSL/TLS Certificates
+
+_Read this document for a brief overview of SSL/TLS certificates in
+the scope of GitLab Pages, for beginners in web development._
+
+Every GitLab Pages project on GitLab.com will be available under
+HTTPS for the default Pages domain (`*.gitlab.io`). Once you set
+up your Pages project with your custom (sub)domain, if you want
+it secured by HTTPS, you will have to issue a certificate for that
+(sub)domain and install it on your project.
+
+NOTE: **Note:**
+Certificates are NOT required to add to your custom
+(sub)domain on your GitLab Pages project, though they are
+highly recommendable.
+
+Let's start with an introduction to the importance of HTTPS.
+
+## Why should I care about HTTPS?
+
+This might be your first question. If our sites are hosted by GitLab Pages,
+they are static, hence we are not dealing with server-side scripts
+nor credit card transactions, then why do we need secure connections?
+
+Back in the 1990s, where HTTPS came out, [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security#SSL_1.0.2C_2.0_and_3.0) was considered a "special"
+security measure, necessary just for big companies, like banks and shoppings sites
+with financial transactions.
+Now we have a different picture. [According to Josh Aas](https://letsencrypt.org/2015/10/29/phishing-and-malware.html), Executive Director at [ISRG](https://en.wikipedia.org/wiki/Internet_Security_Research_Group):
+
+> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](https://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._
+
+Therefore, the reason why certificates are so important is that they encrypt
+the connection between the **client** (you, me, your visitors)
+and the **server** (where you site lives), through a keychain of
+authentications and validations.
+
+How about taking Josh's advice and protecting our sites too? We will be
+well supported, and we'll contribute to a safer internet.
+
+## Organizations supporting HTTPS
+
+There is a huge movement in favor of securing all the web. W3C fully
+[supports the cause](https://w3ctag.github.io/web-https/) and explains very well
+the reasons for that. Richard Barnes, a writer for Mozilla Security Blog,
+suggested that [Firefox would deprecate HTTP](https://blog.mozilla.org/security/2015/04/30/deprecating-non-secure-http/),
+and would no longer accept unsecured connections. Recently, Mozilla published a
+[communication](https://blog.mozilla.org/security/2016/03/29/march-2016-ca-communication/)
+reiterating the importance of HTTPS.
+
+## Issuing Certificates
+
+GitLab Pages accepts certificates provided in the [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) format, issued by
+[Certificate Authorities (CAs)](https://en.wikipedia.org/wiki/Certificate_authority) or as
+[self-signed certificates](https://en.wikipedia.org/wiki/Self-signed_certificate). Note that [self-signed certificates are typically not used](https://securingtomorrow.mcafee.com/other-blogs/mcafee-labs/self-signed-certificates-secure-so-why-ban/)
+for public websites for security reasons and to ensure that browsers trust your site's certificate.
+
+There are various kinds of certificates, each one
+with a certain security level. A static personal website will
+not require the same security level as an online banking web app,
+for instance.
+
+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).
+
+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/).
+Their certs are valid up to 15 years. See the tutorial on
+[how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/).
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index bc9a11504cd..9bc9fe97fd3 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -1,314 +1 @@
----
-last_updated: 2019-06-25
-type: concepts, reference, howto
----
-
-# GitLab Pages custom domains and SSL/TLS Certificates
-
-Setting up GitLab Pages with custom domains, and adding SSL/TLS certificates to them, are optional features of GitLab Pages.
-
-These steps assume you've already [set your site up](getting_started_part_two.md) and it's served under the default Pages domain `namespace.gitlab.io`, or `namespace.gitlab.io/project-name`.
-
-## Adding your custom domain to GitLab Pages
-
-To use one or more custom domain with your Pages site, there are two things
-you should consider first, which we'll cover in this guide:
-
-1. Either if you're adding a **root domain** or a **subdomain**, for which
- you'll need to set up [DNS records](#dns-records)
-1. Whether you want to add an [SSL/TLS certificate](#ssltls-certificates) or not
-
-To finish the association, you need to [add your domain to your project's Pages settings](#add-your-custom-domain-to-gitlab-pages-settings).
-
-Let's start from the beginning with [DNS records](#dns-records).
-If you already know how they work and want to skip the introduction to DNS,
-you may be interested in skipping it until the [TL;DR](#tldr) section below.
-
-### DNS Records
-
-A Domain Name System (DNS) web service routes visitors to websites
-by translating domain names (such as `www.example.com`) into the
-numeric IP addresses (such as `192.0.2.1`) that computers use to
-connect to each other.
-
-A DNS record is created to point a (sub)domain to a certain location,
-which can be an IP address or another domain. In case you want to use
-GitLab Pages with your own (sub)domain, you need to access your domain's
-registrar control panel to add a DNS record pointing it back to your
-GitLab Pages site.
-
-Note that **how to** add DNS records depends on which server your domain
-is hosted on. Every control panel has its own place to do it. If you are
-not an admin of your domain, and don't have access to your registrar,
-you'll need to ask for the technical support of your hosting service
-to do it for you.
-
-To help you out, we've gathered some instructions on how to do that
-for the most popular hosting services:
-
-- [Amazon](http://docs.aws.amazon.com/gettingstarted/latest/swh/getting-started-configure-route53.html)
-- [Bluehost](https://my.bluehost.com/cgi/help/559)
-- [CloudFlare](https://support.cloudflare.com/hc/en-us/articles/200169096-How-do-I-add-A-records-)
-- [cPanel](https://documentation.cpanel.net/display/ALD/Edit+DNS+Zone)
-- [DreamHost](https://help.dreamhost.com/hc/en-us/articles/215414867-How-do-I-add-custom-DNS-records-)
-- [Go Daddy](https://www.godaddy.com/help/add-an-a-record-19238)
-- [Hostgator](http://support.hostgator.com/articles/changing-dns-records)
-- [Inmotion hosting](https://my.bluehost.com/cgi/help/559)
-- [Media Temple](https://mediatemple.net/community/products/dv/204403794/how-can-i-change-the-dns-records-for-my-domain)
-- [Microsoft](https://msdn.microsoft.com/en-us/library/bb727018.aspx)
-
-If your hosting service is not listed above, you can just try to
-search the web for `how to add dns record on <my hosting service>`.
-
-#### DNS A record
-
-In case you want to point a root domain (`example.com`) to your
-GitLab Pages site, deployed to `namespace.gitlab.io`, you need to
-log into your domain's admin control panel and add a DNS `A` record
-pointing your domain to Pages' server IP address. For projects on
-GitLab.com, this IP is `35.185.44.232`. For projects living in
-other GitLab instances (CE or EE), please contact your sysadmin
-asking for this information (which IP address is Pages server
-running on your instance).
-
-**Practical Example:**
-
-![DNS A record pointing to GitLab.com Pages server](img/dns_add_new_a_record_example_updated_2018.png)
-
-CAUTION: **Caution:**
-Note that if you use your root domain for your GitLab Pages website
-**only**, and if your domain registrar supports this feature, you can
-add a DNS apex `CNAME` record instead of an `A` record. The main
-advantage of doing so is that when GitLab Pages IP on GitLab.com
-changes for whatever reason, you don't need to update your `A` record.
-There may be a few exceptions, but **this method is not recommended**
-as it most likely won't work if you set an `MX` record for your root domain.
-
-#### DNS CNAME record
-
-In case you want to point a subdomain (`hello-world.example.com`)
-to your GitLab Pages site initially deployed to `namespace.gitlab.io`,
-you need to log into your domain's admin control panel and add a DNS
-`CNAME` record pointing your subdomain to your website URL
-(`namespace.gitlab.io`) address.
-
-Note that, whether it's a user or a project website, the `CNAME`
-should point to your Pages domain (`namespace.gitlab.io`),
-without any `/project-name`.
-
-**Practical Example:**
-
-![DNS CNAME record pointing to GitLab.com project](img/dns_cname_record_example.png)
-
-#### DNS TXT record
-
-Unless your GitLab administrator has [disabled custom domain verification](../../../administration/pages/index.md#custom-domain-verification),
-you'll have to prove that you own the domain by creating a `TXT` record
-containing a verification code. The code will be displayed after you
-[add your custom domain to GitLab Pages settings](#add-your-custom-domain-to-gitlab-pages-settings).
-
-If using a [DNS A record](#dns-a-record), you can place the TXT record directly
-under the domain. If using a [DNS CNAME record](#dns-cname-record), the two record types won't
-co-exist, so you need to place the TXT record in a special subdomain of its own.
-
-If the domain cannot be verified for 7 days, it will be removed from the GitLab project.
-
-#### TL;DR
-
-For root domains (`domain.com`), set a DNS `A` record and verify your
-domain's ownership with a TXT record:
-
-| From | DNS Record | To |
-| ---- | ---------- | -- |
-| domain.com | A | 35.185.44.232 |
-| domain.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff |
-
-For subdomains (`subdomain.domain.com`), set a DNS `CNAME` record and
-verify your domain's ownership with a TXT record:
-
-| From | DNS Record | To |
-| ---- | ---------- | -- |
-| subdomain.domain.com | CNAME | namespace.gitlab.io |
-| _gitlab-pages-verification-code.subdomain.domain.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff |
-
-> **Notes**:
->
-> - **Do not** use a CNAME record if you want to point your
- `domain.com` to your GitLab Pages site. Use an `A` record instead.
-> - **Do not** add any special chars after the default Pages
- domain. E.g., **do not** point your `subdomain.domain.com` to
- `namespace.gitlab.io.` or `namespace.gitlab.io/`.
-> - GitLab Pages IP on GitLab.com [was changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) in 2017.
-> - GitLab Pages IP on GitLab.com [has been changed](https://about.gitlab.com/2018/07/19/gcp-move-update/#gitlab-pages-and-custom-domains)
- from `52.167.214.135` to `35.185.44.232` in 2018.
-
-### Add your custom domain to GitLab Pages settings
-
-Once you've set the DNS record, you'll need navigate to your project's
-**Setting > Pages** and click **+ New domain** to add your custom domain to
-GitLab Pages. You can choose whether to add an [SSL/TLS certificate](#ssltls-certificates)
-to make your website accessible under HTTPS or leave it blank. If you don't add a certificate,
-your site will be accessible only via HTTP:
-
-![Add new domain](img/add_certificate_to_pages.png)
-
-Once you have added a new domain, you will need to **verify your ownership**
-(unless the GitLab administrator has disabled this feature). A verification code
-will be shown to you; add it as a [DNS TXT record](#dns-txt-record), then press
-the "Verify ownership" button to activate your new domain:
-
-![Verify your domain](img/verify_your_domain.png)
-
-Once your domain has been verified, leave the verification record in place -
-your domain will be periodically reverified, and may be disabled if the record
-is removed.
-
-You can add more than one alias (custom domains and subdomains) to the same project.
-An alias can be understood as having many doors leading to the same room.
-
-All the aliases you've set to your site will be listed on **Setting > Pages**.
-From that page, you can view, add, and remove them.
-
-Note that [DNS propagation may take some time (up to 24h)](http://www.inmotionhosting.com/support/domain-names/dns-nameserver-changes/domain-names-dns-changes),
-although it's usually a matter of minutes to complete. Until it does, verification
-will fail and attempts to visit your domain will respond with a 404.
-
-### Redirecting `www.domain.com` to `domain.com` with Cloudflare
-
-If you use Cloudflare, you can redirect `www` to `domain.com` without the need of adding both
-`www.domain.com` and `domain.com` to GitLab. This happens due to a [Cloudflare feature that creates
-a 301 redirect as a "page rule"](https://gitlab.com/gitlab-org/gitlab-ce/issues/48848#note_87314849) for redirecting `www.domain.com` to `domain.com`. In this case,
-you can use the following setup:
-
-- In Cloudflare, create a DNS `A` record pointing `domain.com` to `35.185.44.232`
-- In GitLab, add the domain to GitLab Pages
-- In Cloudflare, create a DNS `TXT` record to verify your domain
-- In Cloudflare, create a DNS `CNAME` record pointing `www` to `domain.com`
-
-## SSL/TLS Certificates
-
-Every GitLab Pages project on GitLab.com will be available under
-HTTPS for the default Pages domain (`*.gitlab.io`). Once you set
-up your Pages project with your custom (sub)domain, if you want
-it secured by HTTPS, you will have to issue a certificate for that
-(sub)domain and install it on your project.
-
->**Note:**
-Certificates are NOT required to add to your custom
-(sub)domain on your GitLab Pages project, though they are
-highly recommendable.
-
-Let's start with an introduction to the importance of HTTPS.
-Alternatively, jump ahead to [adding certificates to your project](#adding-certificates-to-pages).
-
-### Why should I care about HTTPS?
-
-This might be your first question. If our sites are hosted by GitLab Pages,
-they are static, hence we are not dealing with server-side scripts
-nor credit card transactions, then why do we need secure connections?
-
-Back in the 1990s, where HTTPS came out, [SSL](https://en.wikipedia.org/wiki/Transport_Layer_Security#SSL_1.0.2C_2.0_and_3.0) was considered a "special"
-security measure, necessary just for big companies, like banks and shoppings sites
-with financial transactions.
-Now we have a different picture. [According to Josh Aas](https://letsencrypt.org/2015/10/29/phishing-and-malware.html), Executive Director at [ISRG](https://en.wikipedia.org/wiki/Internet_Security_Research_Group):
-
-> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](https://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._
-
-Therefore, the reason why certificates are so important is that they encrypt
-the connection between the **client** (you, me, your visitors)
-and the **server** (where you site lives), through a keychain of
-authentications and validations.
-
-How about taking Josh's advice and protecting our sites too? We will be
-well supported, and we'll contribute to a safer internet.
-
-### Organizations supporting HTTPS
-
-There is a huge movement in favor of securing all the web. W3C fully
-[supports the cause](https://w3ctag.github.io/web-https/) and explains very well
-the reasons for that. Richard Barnes, a writer for Mozilla Security Blog,
-suggested that [Firefox would deprecate HTTP](https://blog.mozilla.org/security/2015/04/30/deprecating-non-secure-http/),
-and would no longer accept unsecured connections. Recently, Mozilla published a
-[communication](https://blog.mozilla.org/security/2016/03/29/march-2016-ca-communication/)
-reiterating the importance of HTTPS.
-
-## Issuing Certificates
-
-GitLab Pages accepts certificates provided in the [PEM](https://support.quovadisglobal.com/kb/a37/what-is-pem-format.aspx) format, issued by
-[Certificate Authorities (CAs)](https://en.wikipedia.org/wiki/Certificate_authority) or as
-[self-signed certificates](https://en.wikipedia.org/wiki/Self-signed_certificate). Note that [self-signed certificates are typically not used](https://securingtomorrow.mcafee.com/other-blogs/mcafee-labs/self-signed-certificates-secure-so-why-ban/)
-for public websites for security reasons and to ensure that browsers trust your site's certificate.
-
-There are various kinds of certificates, each one
-with a certain security level. A static personal website will
-not require the same security level as an online banking web app,
-for instance.
-
-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).
-
-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/).
-Their certs are valid up to 15 years. See the tutorial on
-[how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/).
-
-### Adding certificates to Pages
-
-Regardless the CA you choose, the steps to add your certificate to
-your Pages project are the same.
-
-#### Requirements
-
-1. A PEM certificate
-1. An intermediate certificate
-1. A private key
-
-![Pages project - adding certificates](img/add_certificate_to_pages.png)
-
-These fields are found under your **Project**'s **Settings** > **Pages** > **New Domain**.
-
-#### Certificate types
-
-- A PEM certificate is the certificate generated by the CA,
- which needs to be added to the field **Certificate (PEM)**.
-- An [intermediate certificate](https://en.wikipedia.org/wiki/Intermediate_certificate_authority) (aka "root certificate") is
- the part of the encryption keychain that identifies the CA.
- Usually it's combined with the PEM certificate, but there are
- some cases in which you need to add them manually.
- [CloudFlare certs](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/)
- are one of these cases.
-- A private key is an encrypted key which validates
- your PEM against your domain.
-
-#### Add the certificate to your project
-
-Once you've met the requirements:
-
-- Your PEM certificate needs to be added to the first field.
-- If your certificate is missing its intermediate, copy
- and paste the root certificate (usually available from your CA website)
- and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/),
- just jumping a line between them.
-- Copy your private key and paste it in the last field.
-
-NOTE: **Note:**
-**Do not** open certificates or encryption keys in
-regular text editors. Always use code editors (such as
-Sublime Text, Atom, Dreamweaver, Brackets, etc).
-
-## Force HTTPS for GitLab Pages websites
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28857) in GitLab 10.7.
-
-To make your website's visitors even more secure, you can choose to
-force HTTPS for GitLab Pages. By doing so, all attempts to visit your
-website via HTTP will be automatically redirected to HTTPS via 301.
-
-It works with both GitLab's default domain and with your custom
-domain (as long as you've set a valid certificate for it).
-
-To enable this setting, navigate to your project's **Settings > Pages**
-and tick the checkbox **Force HTTPS (requires valid certificates)**.
+This document was moved to [another location](custom_domains_ssl_tls_certification/index.md).
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index fe92d19567d..743c360f075 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -22,7 +22,7 @@ Optional Features:
1. **Optional**: an SSL/TLS certificate so your custom
domain is accessible under HTTPS.
-The optional settings, custom domain, DNS records, and SSL/TLS certificates, are described in [Part 3](getting_started_part_three.md)).
+The optional settings, custom domain, DNS records, and SSL/TLS certificates, are described in [GitLab Pages custom domains and SSL/TLS Certificates](custom_domains_ssl_tls_certification/index.md)).
## Project
@@ -169,4 +169,4 @@ baseurl: ""
## Custom Domains
GitLab Pages supports custom domains and subdomains, served under HTTP or HTTPS.
-See [GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md) for more information.
+See [GitLab Pages custom domains and SSL/TLS Certificates](custom_domains_ssl_tls_certification/index.md) for more information.
diff --git a/doc/user/project/pages/img/pages_project_templates_11-8.png b/doc/user/project/pages/img/pages_project_templates_v11_8.png
index a645d28260b..a645d28260b 100644
--- a/doc/user/project/pages/img/pages_project_templates_11-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/verify_your_domain.png b/doc/user/project/pages/img/verify_your_domain.png
deleted file mode 100644
index d870f9e6505..00000000000
--- a/doc/user/project/pages/img/verify_your_domain.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 64b1e259292..25944b029d7 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_11-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
@@ -115,8 +115,8 @@ will run a new pipeline to publish your changes to the server.
_Advanced options:_
-- [Use a custom domain](getting_started_part_three.md#adding-your-custom-domain-to-gitlab-pages)
-- Apply [SSL/TLS certification](getting_started_part_three.md#ssltls-certificates) to your custom domain
+- [Use a custom domain](custom_domains_ssl_tls_certification/index.md#set-up-pages-with-a-custom-domain)
+- Apply [SSL/TLS certification](custom_domains_ssl_tls_certification/index.md#adding-an-ssltls-certificate-to-pages) to your custom domain
## Availability
@@ -142,9 +142,9 @@ To learn more about configuration options for GitLab Pages, read the following:
| [GitLab CI/CD for GitLab Pages](getting_started_part_four.md) | Understand how to create your own `.gitlab-ci.yml` for your site. |
| [Exploring GitLab Pages](introduction.md) | Requirements, technical aspects, specific GitLab CI's configuration options, Access Control, custom 404 pages, limitations, FAQ. |
|---+---|
-| [Custom domains and SSL/TLS Certificates](getting_started_part_three.md) | How to add custom domains and subdomains to your website, configure DNS records and SSL/TLS certificates. |
+| [Custom domains and SSL/TLS Certificates](custom_domains_ssl_tls_certification/index.md) | How to add custom domains and subdomains to your website, configure DNS records and SSL/TLS certificates. |
+| [Let's Encrypt integration](custom_domains_ssl_tls_certification/lets_encrypt_integration.md) | Secure your Pages sites with Let's Encrypt certificates automatically obtained and renewed by GitLab. |
| [CloudFlare certificates](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) | Secure your Pages site with CloudFlare certificates. |
-| [Let's Encrypt certificates](lets_encrypt_for_gitlab_pages.md) | Secure your Pages site with Let's Encrypt certificates. |
|---+---|
| [Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) | A conceptual overview on static versus dynamic sites. |
| [Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) | A conceptual overview on SSGs. |
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/pages/lets_encrypt_for_gitlab_pages.md b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
index 91a660c0f7a..1338c7e58f5 100644
--- a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
+++ b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
@@ -1,10 +1,15 @@
---
-description: "How to secure GitLab Pages websites with Let's Encrypt."
+description: "How to secure GitLab Pages websites with Let's Encrypt (manual process, deprecated)."
type: howto
-last_updated: 2019-06-04
+last_updated: 2019-07-15
---
-# Let's Encrypt for GitLab Pages
+# Let's Encrypt for GitLab Pages (manual process, deprecated)
+
+CAUTION: **Warning:**
+This method is still valid but was **deprecated** in favor of the
+[Let's Encrypt integration](custom_domains_ssl_tls_certification/lets_encrypt_integration.md)
+introduced in GitLab 12.1.
If you have a GitLab Pages website served under your own domain,
you might want to secure it with a SSL/TSL certificate.
@@ -18,9 +23,9 @@ To follow along with this tutorial, we assume you already have:
- Created a [project](getting_started_part_two.md) in GitLab which
contains your website's source code.
-- Acquired a domain (`example.com`) and added a [DNS entry](getting_started_part_three.md#dns-records)
+- Acquired a domain (`example.com`) and added a [DNS entry](custom_domains_ssl_tls_certification/index.md#set-up-pages-with-a-custom-domain)
pointing it to your Pages website.
-- [Added your domain to your Pages project](getting_started_part_three.md#add-your-custom-domain-to-gitlab-pages-settings)
+- [Added your domain to your Pages project](custom_domains_ssl_tls_certification/index.md#steps)
and verified your ownership.
- Cloned your project into your computer.
- Your website up and running, served under HTTP protocol at `http://example.com`.
@@ -36,111 +41,111 @@ operating systems the steps might be slightly different. Follow the
[CertBot instructions](https://certbot.eff.org/) according to your OS.
1. On your computer, open a terminal and navigate to your repository's
- root directory:
+ root directory:
- ```bash
- cd path/to/dir
- ```
+ ```bash
+ cd path/to/dir
+ ```
1. Install CertBot (the tool Let's Encrypt uses to issue certificates):
- ```bash
- brew install certbot
- ```
+ ```bash
+ brew install certbot
+ ```
1. Request a certificate for your domain (`example.com`) and
- provide an email account (`your@email.com`) to receive notifications:
+ provide an email account (`your@email.com`) to receive notifications:
- ```bash
- sudo certbot certonly -a manual -d example.com --email your@email.com
- ```
+ ```bash
+ sudo certbot certonly -a manual -d example.com --email your@email.com
+ ```
- Alternatively, you can register without adding an e-mail account,
- but you won't be notified about the certificate expiration's date:
+ Alternatively, you can register without adding an e-mail account,
+ but you won't be notified about the certificate expiration's date:
- ```bash
- sudo certbot certonly -a manual -d example.com --register-unsafely-without-email
- ```
+ ```bash
+ sudo certbot certonly -a manual -d example.com --register-unsafely-without-email
+ ```
- TIP: **Tip:**
- Read through CertBot's documentation on their
- [command line options](https://certbot.eff.org/docs/using.html#certbot-command-line-options).
+ TIP: **Tip:**
+ Read through CertBot's documentation on their
+ [command line options](https://certbot.eff.org/docs/using.html#certbot-command-line-options).
1. You'll be prompted with a message to agree with their terms.
- Press `A` to agree and `Y` to let they log your IP.
+ Press `A` to agree and `Y` to let they log your IP.
- CertBot will then prompt you with the following message:
+ CertBot will then prompt you with the following message:
- ```bash
- Create a file containing just this data:
+ ```bash
+ Create a file containing just this data:
- Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP.HUGNKk82jlsmOOfphlt8Jy69iuglsn095nxOMH9j3Yb
+ Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP.HUGNKk82jlsmOOfphlt8Jy69iuglsn095nxOMH9j3Yb
- And make it available on your web server at this URL:
+ And make it available on your web server at this URL:
- http://example.com/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP
+ http://example.com/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP
- Press Enter to Continue
- ```
+ Press Enter to Continue
+ ```
1. **Do not press Enter yet.** Let's Encrypt will need to verify your
- domain ownership before issuing the certificate. To do so, create 3
- consecutive directories under your website's root:
- `/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP/`
- and add to the last folder an `index.html` file containing the content
- referred on the previous prompt message:
-
- ```bash
- Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP.HUGNKk82jlsmOOfphlt8Jy69iuglsn095nxOMH9j3Yb
- ```
-
- Note that this file needs to be accessed under
- `http://example.com/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP`
- to allow Let's Encrypt to verify the ownership of your domain,
- therefore, it needs to be part of the website content under the
- repo's [`public`](index.md#how-it-works) folder.
+ domain ownership before issuing the certificate. To do so, create 3
+ consecutive directories under your website's root:
+ `/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP/`
+ and add to the last folder an `index.html` file containing the content
+ referred on the previous prompt message:
+
+ ```bash
+ Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP.HUGNKk82jlsmOOfphlt8Jy69iuglsn095nxOMH9j3Yb
+ ```
+
+ Note that this file needs to be accessed under
+ `http://example.com/.well-known/acme-challenge/Rxnv6WKo95hsuLVX3osmT6LgmzsJKSaK9htlPToohOP`
+ to allow Let's Encrypt to verify the ownership of your domain,
+ therefore, it needs to be part of the website content under the
+ repo's [`public`](index.md#how-it-works) folder.
1. Add, commit, and push the file into your repo in GitLab. Once the pipeline
- passes, press **Enter** on your terminal to continue issuing your
- certificate. CertBot will then prompt you with the following message:
-
- ```bash
- Waiting for verification...
- Cleaning up challenges
-
- IMPORTANT NOTES:
- - Congratulations! Your certificate and chain have been saved at:
- /etc/letsencrypt/live/example.com/fullchain.pem
- Your key file has been saved at:
- /etc/letsencrypt/live/example.com/privkey.pem
- Your cert will expire on 2019-03-12. To obtain a new or tweaked
- version of this certificate in the future, simply run certbot
- again. To non-interactively renew *all* of your certificates, run
- "certbot renew"
- - If you like Certbot, please consider supporting our work by:
-
- Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
- Donating to EFF: https://eff.org/donate-le
- ```
+ passes, press **Enter** on your terminal to continue issuing your
+ certificate. CertBot will then prompt you with the following message:
+
+ ```bash
+ Waiting for verification...
+ Cleaning up challenges
+
+ IMPORTANT NOTES:
+ - Congratulations! Your certificate and chain have been saved at:
+ /etc/letsencrypt/live/example.com/fullchain.pem
+ Your key file has been saved at:
+ /etc/letsencrypt/live/example.com/privkey.pem
+ Your cert will expire on 2019-03-12. To obtain a new or tweaked
+ version of this certificate in the future, simply run certbot
+ again. To non-interactively renew *all* of your certificates, run
+ "certbot renew"
+ - If you like Certbot, please consider supporting our work by:
+
+ Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
+ Donating to EFF: https://eff.org/donate-le
+ ```
## Add your certificate to GitLab Pages
Now that your certificate has been issued, let's add it to your Pages site:
1. Back at GitLab, navigate to your project's **Settings > Pages**,
- find your domain and click **Details** and **Edit** to add your certificate.
+ find your domain and click **Details** and **Edit** to add your certificate.
1. From your terminal, copy and paste the certificate into the first field
- **Certificate (PEM)**:
+ **Certificate (PEM)**:
- ```bash
- sudo cat /etc/letsencrypt/live/example.com/fullchain.pem | pbcopy
- ```
+ ```bash
+ sudo cat /etc/letsencrypt/live/example.com/fullchain.pem | pbcopy
+ ```
1. Copy and paste the private key into the second field **Key (PEM)**:
- ```bash
- sudo cat /etc/letsencrypt/live/example.com/privkey.pem | pbcopy
- ```
+ ```bash
+ sudo cat /etc/letsencrypt/live/example.com/privkey.pem | pbcopy
+ ```
1. Click **Save changes** to apply them to your website.
1. Wait a few minutes for the configuration changes to take effect.
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 1966b136e9d..2411744c874 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -67,8 +67,6 @@ artifacts in case you changed your mind and want to keep them.
![Job artifacts browser button](img/job_artifacts_browser_button.png)
----
-
The archive browser shows the name and the actual file size of each file in the
archive. If your artifacts contained directories, then you are also able to
browse inside them.
@@ -80,8 +78,6 @@ one HTML file that you can view directly online when
![Job artifacts browser](img/job_artifacts_browser.png)
----
-
## Downloading artifacts
If you need to download the whole archive, there are buttons in various places
@@ -90,22 +86,22 @@ inside GitLab that make that possible.
1. While on the pipelines page, you can see the download icon for each job's
artifacts archive in the right corner:
- ![Job artifacts in Pipelines page](img/job_artifacts_pipelines_page.png)
+ ![Job artifacts in Pipelines page](img/job_artifacts_pipelines_page.png)
1. While on the **Jobs** page, you can see the download icon for each job's
artifacts archive in the right corner:
- ![Job artifacts in Builds page](img/job_artifacts_builds_page.png)
+ ![Job artifacts in Builds page](img/job_artifacts_builds_page.png)
1. While inside a specific job, you are presented with a download button
along with the one that browses the archive:
- ![Job artifacts browser button](img/job_artifacts_browser_button.png)
+ ![Job artifacts browser button](img/job_artifacts_browser_button.png)
1. And finally, when browsing an archive you can see the download button at
the top right corner:
- ![Job artifacts browser](img/job_artifacts_browser.png)
+ ![Job artifacts browser](img/job_artifacts_browser.png)
## Downloading the latest artifacts
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index 24e15a37a40..df82daa3da3 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -25,10 +25,10 @@ in `.gitlab-ci.yml`.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28919) in GitLab 12.0.
NOTE: **Note**:
-As of GitLab 12.0, newly created projects will automatically have a default
+As of GitLab 12.0, newly created projects will automatically have a default
`git depth` value of `50`.
-It is possible to limit the number of changes that GitLab CI/CD will fetch when cloning
+It is possible to limit the number of changes that GitLab CI/CD will fetch when cloning
a repository. Setting a limit to `git depth` can speed up Pipelines execution. Maximum
allowed value is `1000`.
@@ -89,6 +89,22 @@ in the jobs table.
A few examples of known coverage tools for a variety of languages can be found
in the pipelines settings page.
+### Removing color codes
+
+Some test coverage tools output with ANSI color codes that won't be
+parsed correctly by the regular expression and will cause coverage
+parsing to fail.
+
+If your coverage tool doesn't provide an option to disable color
+codes in the output, you can pipe the output of the coverage tool through a
+small one line script that will strip the color codes off.
+
+For example:
+
+```bash
+lein cloverage | perl -pe 's/\e\[?.*?[\@-~]//g'
+```
+
## Visibility of pipelines
Access to pipelines and job details (including output of logs and artifacts)
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 8b8aa51b6dd..f81669a41c9 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -34,19 +34,19 @@ discussions, and descriptions:
| `/remove_milestone` | Remove milestone | ✓ | ✓ |
| `/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 label | ✓ | ✓ |
-| <code>/copy_metadata &lt;#issue &#124; !merge_request&gt;</code> | Copy labels and milestone from other issue or merge request in the project | ✓ | ✓ |
-| <code>/estimate &lt;1w 3d 2h 14m&gt;</code> | Set time estimate | ✓ | ✓ |
+| `/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 | ✓ | ✓ |
+| `/estimate <1w 3d 2h 14m>` | Set time estimate | ✓ | ✓ |
| `/remove_estimate` | Remove time estimate | ✓ | ✓ |
-| <code>/spend &lt;time(1h 30m &#124; -1h 5m)&gt; &lt;date(YYYY-MM-DD)&gt;</code> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
+| `/spend <time(1h 30m | -1h 5m)> <date(YYYY-MM-DD)>` | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
| `/remove_time_spent` | Remove time spent | ✓ | ✓ |
-| `/lock` | Lock the discussion | ✓ | ✓ |
-| `/unlock` | Unlock the discussion | ✓ | ✓ |
-| <code>/due &lt;in 2 days &#124; this Friday &#124; December 31st&gt;</code>| Set due date | ✓ | |
+| `/lock` | Lock the thread | ✓ | ✓ |
+| `/unlock` | Unlock the thread | ✓ | ✓ |
+| `/due <in 2 days | this Friday | December 31st>`| Set due date | ✓ | |
| `/remove_due_date` | Remove due date | ✓ | |
-| <code>/weight &lt;0 &#124; 1 &#124; 2 &#124; ...&gt;</code> | Set weight **(STARTER)** | ✓ | |
+| `/weight <0 | 1 | 2 | ...>` | Set weight **(STARTER)** | ✓ | |
| `/clear_weight` | Clears weight **(STARTER)** | ✓ | |
-| <code>/epic &lt;&epic &#124; group&epic &#124; Epic URL&gt;</code> | Add to epic **(ULTIMATE)** | ✓ | |
+| `/epic <&epic | group&epic | Epic URL>` | Add to epic **(ULTIMATE)** | ✓ | |
| `/remove_epic` | Removes from epic **(ULTIMATE)** | ✓ | |
| `/promote` | Promote issue to epic **(ULTIMATE)** | ✓ | |
| `/confidential` | Make confidential | ✓ | |
@@ -59,6 +59,12 @@ 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 for commit messages
The following quick actions are applicable for commit messages:
@@ -85,8 +91,8 @@ The following quick actions are applicable for epics threads and description:
| `/award :emoji:` | Toggle emoji award |
| `/label ~label1 ~label2` | Add label(s) |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s) |
-| `/relabel ~label1 ~label2` | Replace label |
-| <code>/child_epic &lt;&epic &#124; group&epic &#124; Epic URL&gt;</code> | Adds child epic to epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) |
-| <code>/remove_child_epic &lt;&epic &#124; group&epic &#124; Epic URL&gt;</code> | Removes child epic from epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) |
-| <code>/parent_epic &lt;&epic &#124; group&epic &#124; Epic URL&gt;</code> | Sets parent epic to epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
-| <code>/remove_parent_epic | Removes parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
+| `/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)) |
+| `/remove_parent_epic` | Removes parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index cf0a986887e..3b0a045ef9c 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -45,94 +45,95 @@ started:
1. Generate the private/public key pair with the following command, which will
spawn a series of questions:
- ```sh
- gpg --full-gen-key
- ```
+ ```sh
+ gpg --full-gen-key
+ ```
- NOTE: **Note:**
- In some cases like Gpg4win on Windows and other macOS versions, the command
- here may be `gpg --gen-key`.
+ NOTE: **Note:**
+ In some cases like Gpg4win on Windows and other macOS versions, the command
+ here may be `gpg --gen-key`.
1. The first question is which algorithm can be used. Select the kind you want
or press <kbd>Enter</kbd> to choose the default (RSA and RSA):
- ```
- Please select what kind of key you want:
- (1) RSA and RSA (default)
- (2) DSA and Elgamal
- (3) DSA (sign only)
- (4) RSA (sign only)
- Your selection? 1
- ```
+ ```
+ Please select what kind of key you want:
+ (1) RSA and RSA (default)
+ (2) DSA and Elgamal
+ (3) DSA (sign only)
+ (4) RSA (sign only)
+ Your selection? 1
+ ```
1. The next question is key length. We recommend to choose the highest value
which is `4096`:
- ```
- RSA keys may be between 1024 and 4096 bits long.
- What keysize do you want? (2048) 4096
- Requested keysize is 4096 bits
- ```
+ ```
+ RSA keys may be between 1024 and 4096 bits long.
+ What keysize do you want? (2048) 4096
+ 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:
- ```
- Please specify how long the key should be valid.
- 0 = key does not expire
- <n> = key expires in n days
- <n>w = key expires in n weeks
- <n>m = key expires in n months
- <n>y = key expires in n years
- Key is valid for? (0) 0
- Key does not expire at all
- ```
+ ```
+ Please specify how long the key should be valid.
+ 0 = key does not expire
+ <n> = key expires in n days
+ <n>w = key expires in n weeks
+ <n>m = key expires in n months
+ <n>y = key expires in n years
+ Key is valid for? (0) 0
+ Key does not expire at all
+ ```
1. Confirm that the answers you gave were correct by typing `y`:
- ```
- Is this correct? (y/N) y
- ```
+ ```
+ 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):
- ```
- GnuPG needs to construct a user ID to identify your key.
+ ```
+ GnuPG needs to construct a user ID to identify your key.
- Real name: Mr. Robot
- Email address: <your_email>
- Comment:
- You selected this USER-ID:
- "Mr. Robot <your_email>"
+ Real name: Mr. Robot
+ Email address: <your_email>
+ Comment:
+ You selected this USER-ID:
+ "Mr. Robot <your_email>"
- Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
- ```
+ Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
+ ```
1. Pick a strong password when asked and type it twice to confirm.
1. Use the following command to list the private GPG key you just created:
- ```
- gpg --list-secret-keys --keyid-format LONG <your_email>
- ```
+ ```
+ gpg --list-secret-keys --keyid-format LONG <your_email>
+ ```
- Replace `<your_email>` with the email address you entered above.
+ Replace `<your_email>` with the email address you entered above.
1. Copy the GPG key ID that starts with `sec`. In the following example, that's
`30F2B65B9246B6CA`:
- ```
- sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
- D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA
- uid [ultimate] Mr. Robot <your_email>
- ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
- ```
+ ```
+ sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
+ D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA
+ uid [ultimate] Mr. Robot <your_email>
+ ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
+ ```
1. Export the public key of that ID (replace your key ID from the previous step):
- ```
- gpg --armor --export 30F2B65B9246B6CA
- ```
+ ```
+ gpg --armor --export 30F2B65B9246B6CA
+ ```
1. Finally, copy the public key and [add it in your profile settings](#adding-a-gpg-key-to-your-account)
@@ -146,17 +147,17 @@ You can add a GPG key in your profile's settings:
1. On the upper right corner, click on your avatar and go to your **Settings**.
- ![Settings dropdown](../../../profile/img/profile_settings_dropdown.png)
+ ![Settings dropdown](../../../profile/img/profile_settings_dropdown.png)
1. Navigate to the **GPG keys** tab and paste your _public_ key in the 'Key'
box.
- ![Paste GPG public key](img/profile_settings_gpg_keys_paste_pub.png)
+ ![Paste GPG public key](img/profile_settings_gpg_keys_paste_pub.png)
1. Finally, click on **Add key** to add it to GitLab. You will be able to see
its fingerprint, the corresponding email address and creation date.
- ![GPG key single page](img/profile_settings_gpg_keys_single_key.png)
+ ![GPG key single page](img/profile_settings_gpg_keys_single_key.png)
## Associating your GPG key with Git
@@ -166,29 +167,29 @@ key to use.
1. Use the following command to list the private GPG key you just created:
- ```sh
- gpg --list-secret-keys --keyid-format LONG <your_email>
- ```
+ ```sh
+ gpg --list-secret-keys --keyid-format LONG <your_email>
+ ```
- Replace `<your_email>` with the email address you entered above.
+ Replace `<your_email>` with the email address you entered above.
1. Copy the GPG key ID that starts with `sec`. In the following example, that's
`30F2B65B9246B6CA`:
- ```
- sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
- D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA
- uid [ultimate] Mr. Robot <your_email>
- ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
- ```
+ ```
+ sec rsa4096/30F2B65B9246B6CA 2017-08-18 [SC]
+ D5E4F29F3275DC0CDA8FFC8730F2B65B9246B6CA
+ uid [ultimate] Mr. Robot <your_email>
+ ssb rsa4096/B7ABC0813E4028C0 2017-08-18 [E]
+ ```
1. Tell Git to use that key to sign the commits:
- ```sh
- git config --global user.signingkey 30F2B65B9246B6CA
- ```
+ ```sh
+ git config --global user.signingkey 30F2B65B9246B6CA
+ ```
- Replace `30F2B65B9246B6CA` with your GPG key ID.
+ Replace `30F2B65B9246B6CA` with your GPG key ID.
1. (Optional) If Git is using `gpg` and you get errors like `secret key not available`
or `gpg: signing failed: secret key not available`, run the following command to
@@ -206,9 +207,9 @@ commits:
1. Commit like you used to, the only difference is the addition of the `-S` flag:
- ```
- git commit -S -m "My commit msg"
- ```
+ ```
+ git commit -S -m "My commit msg"
+ ```
1. Enter the passphrase of your GPG key when asked.
1. Push to GitLab and check that your commits [are verified](#verifying-commits).
@@ -227,13 +228,13 @@ git config --global commit.gpgsign true
"Verified" or "Unverified", depending on the verification status of the GPG
signature.
- ![Signed and unsigned commits](img/project_signed_and_unsigned_commits.png)
+ ![Signed and unsigned commits](img/project_signed_and_unsigned_commits.png)
1. By clicking on the GPG badge, details of the signature are displayed.
- ![Signed commit with verified signature](img/project_signed_commit_verified_signature.png)
+ ![Signed commit with verified signature](img/project_signed_commit_verified_signature.png)
- ![Signed commit with verified signature](img/project_signed_commit_unverified_signature.png)
+ ![Signed commit with verified signature](img/project_signed_commit_unverified_signature.png)
## Revoking a GPG key
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 7765a3d7438..7c711bc0b3b 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
@@ -54,50 +54,50 @@ removed from the repository.
1. Navigate to your repository:
- ```
- cd my_repository/
- ```
+ ```
+ cd my_repository/
+ ```
1. Change to the branch you want to remove the big file from:
- ```
- git checkout master
- ```
+ ```
+ git checkout master
+ ```
1. Create a commit removing the large file from the branch, if it still exists:
- ```
- git rm path/to/big_file.mpg
- git commit -m 'Remove unneeded large file'
- ```
+ ```
+ git rm path/to/big_file.mpg
+ git commit -m 'Remove unneeded large file'
+ ```
1. Rewrite history:
- ```
- bfg --delete-files path/to/big_file.mpg
- ```
+ ```
+ bfg --delete-files path/to/big_file.mpg
+ ```
- An object map file will be written to `object-id-map.old-new.txt`. Keep it
- around - you'll need it for the final step!
+ An object map file will be written to `object-id-map.old-new.txt`. Keep it
+ around - you'll need it for the final step!
1. Force-push the changes to GitLab:
- ```
- git push --force-with-lease origin master
- ```
+ ```
+ git push --force-with-lease origin master
+ ```
- If this step fails, someone has changed the `master` branch while you were
- rewriting history. You could restore the branch and re-run BFG to preserve
- their changes, or use `git push --force` to overwrite their changes.
+ If this step fails, someone has changed the `master` branch while you were
+ rewriting history. You could restore the branch and re-run BFG to preserve
+ their changes, or use `git push --force` to overwrite their changes.
1. Navigate to **Project > Settings > Repository > Repository Cleanup**:
- ![Repository settings cleanup form](img/repository_cleanup.png)
+ ![Repository settings cleanup form](img/repository_cleanup.png)
- Upload the `object-id-map.old-new.txt` file and press **Start cleanup**.
- This will remove any internal git references to the old commits, and run
- `git gc` against the repository. You will receive an email once it has
- completed.
+ Upload the `object-id-map.old-new.txt` file and press **Start cleanup**.
+ This will remove any internal git references to the old commits, and run
+ `git gc` against the repository. You will receive an email once it has
+ completed.
NOTE: **Note:**
This process will remove some copies of the rewritten commits from GitLab's
@@ -110,32 +110,32 @@ purposes!
1. Navigate to your repository:
- ```
- cd my_repository/
- ```
+ ```
+ cd my_repository/
+ ```
1. Change to the branch you want to remove the big file from:
- ```
- git checkout master
- ```
+ ```
+ git checkout master
+ ```
1. Use `filter-branch` to remove the big file:
- ```
- git filter-branch --force --tree-filter 'rm -f path/to/big_file.mpg' HEAD
- ```
+ ```
+ git filter-branch --force --tree-filter 'rm -f path/to/big_file.mpg' HEAD
+ ```
1. Instruct Git to purge the unwanted data:
- ```
- git reflog expire --expire=now --all && git gc --prune=now --aggressive
- ```
+ ```
+ git reflog expire --expire=now --all && git gc --prune=now --aggressive
+ ```
1. Lastly, force push to the repository:
- ```
- git push --force origin master
- ```
+ ```
+ git push --force origin master
+ ```
Your repository should now be below the size limit.
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index 253e5374f52..0116f0fe7ca 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -13,8 +13,6 @@ Choose **New file** from the dropdown.
![New file dropdown menu](img/web_editor_new_file_dropdown.png)
----
-
Enter a file name in the **File name** box. Then, add file content in the editor
area. Add a descriptive commit message and choose a branch. The branch field
will default to the branch you were viewing in the file browser. If you enter
@@ -59,8 +57,6 @@ selector. Choose **Upload file** from the dropdown.
![Upload file dropdown menu](img/web_editor_upload_file_dropdown.png)
----
-
Once the upload dialog pops up there are two ways to upload your file. Either
drag and drop a file on the pop up or use the **click to upload** link. A file
preview will appear once you have selected a file to upload.
@@ -80,8 +76,6 @@ Choose **New directory** from the dropdown.
![New directory dropdown](img/web_editor_new_directory_dropdown.png)
----
-
In the new directory dialog enter a directory name, a commit message and choose
the target branch. Click **Create directory** to finish.
@@ -129,8 +123,6 @@ choose **New branch** from the dropdown.
![New branch dropdown](img/web_editor_new_branch_dropdown.png)
----
-
Enter a new **Branch name**. Optionally, change the **Create from** field
to choose which branch, tag or commit SHA this new branch will originate from.
This field will autocomplete if you start typing an existing branch or tag.
@@ -139,8 +131,6 @@ branch.
![New branch page](img/web_editor_new_branch_page.png)
----
-
You can now make changes to any files, as needed. When you're ready to merge
the changes back to master you can use the widget at the top of the screen.
This widget only appears for a period of time after you create the branch or
@@ -156,8 +146,6 @@ SHA. From a project's files page, choose **New tag** from the dropdown.
![New tag dropdown](img/web_editor_new_tag_dropdown.png)
----
-
Give the tag a name such as `v1.0.0`. Choose the branch or SHA from which you
would like to create this new tag. You can optionally add a message and
release notes. The release notes section supports markdown format and you can
diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md
index f899120f0c2..645b9bffcc1 100644
--- a/doc/user/project/service_desk.md
+++ b/doc/user/project/service_desk.md
@@ -53,36 +53,35 @@ Service Desk is enabled on GitLab.com. If you're a
[Silver subscriber](https://about.gitlab.com/gitlab-com/),
you can skip the step 1 below; you only need to enable it per project.
-1. [Set up incoming email](../../administration/incoming_email.md#set-it-up) for the GitLab instance. This must
- support [email sub-addressing](../../administration/incoming_email.md#email-sub-addressing).
-2. Navigate to your project's **Settings** and scroll down to the **Service Desk**
- section.
-3. If you have the correct access and an Premium license,
- you will see an option to set up Service Desk:
+1. [Set up incoming email](../../administration/incoming_email.md#set-it-up) for the GitLab instance. This must
+ support [email sub-addressing](../../administration/incoming_email.md#email-sub-addressing).
+1. Navigate to your project's **Settings** and scroll down to the **Service Desk**
+ section.
+1. If you have the correct access and an Premium license,
+ you will see an option to set up Service Desk:
- ![Activate Service Desk option](img/service_desk_disabled.png)
+ ![Activate Service Desk option](img/service_desk_disabled.png)
-4. Checking that box will enable Service Desk for the project, and show a
- unique email address to email issues to the project. These issues will be
- [confidential](issues/confidential_issues.md), so they will only be visible to project members.
+1. Checking that box will enable Service Desk for the project, and show a
+ unique email address to email issues to the project. These issues will be
+ [confidential](issues/confidential_issues.md), so they will only be visible to project members.
- **Warning**: this email address can be used by anyone to create an issue on
- this project, whether or not they have access to your GitLab instance.
- We recommend **putting this behind an alias** so that it can be changed if
- needed, and **[enabling Akismet](../../integration/akismet.md)** on your GitLab instance to add spam
- checking to this service. Unblocked email spam would result in many spam
- issues being created, and may disrupt your GitLab service.
+ **Warning**: this email address can be used by anyone to create an issue on
+ this project, whether or not they have access to your GitLab instance.
+ We recommend **putting this behind an alias** so that it can be changed if
+ needed, and **[enabling Akismet](../../integration/akismet.md)** on your GitLab instance to add spam
+ checking to this service. Unblocked email spam would result in many spam
+ issues being created, and may disrupt your GitLab service.
- ![Service Desk enabled](img/service_desk_enabled.png)
+ ![Service Desk enabled](img/service_desk_enabled.png)
- _In GitLab 11.7, we updated the format of the generated email address.
- However the older format is still supported, allowing existing aliases
- or contacts to continue working._
+ _In GitLab 11.7, we updated the format of the generated email address.
+ However the older format is still supported, allowing existing aliases
+ or contacts to continue working._
+1. Service Desk is now enabled for this project! You should be able to access it from your project's navigation **Issue submenu**:
-5. Service Desk is now enabled for this project! You should be able to access it from your project's navigation **Issue submenu**:
-
- ![Service Desk Navigation Item](img/service_desk_nav_item.png)
+ ![Service Desk Navigation Item](img/service_desk_nav_item.png)
## Using Service Desk
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 819515d7a4c..35d5320c0b1 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -2,33 +2,33 @@
>**Notes:**
>
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/3050) in GitLab 8.9.
-> - Importing will not be possible if the import instance version differs from
-> that of the exporter.
-> - For GitLab admins, please read through
-> [Project import/export administration](../../../administration/raketasks/project_import_export.md).
-> - For existing installations, the project import option has to be enabled in
-> application settings (`/admin/application_settings`) under 'Import sources'.
-> Ask your administrator if you don't see the **GitLab export** button when
-> creating a new project.
-> - Starting with GitLab 10.0, administrators can disable the project export option
-> on the GitLab instance in application settings (`/admin/application_settings`)
-> under 'Visibility and Access Controls'.
-> - You can find some useful raketasks if you are an administrator in the
-> [import_export](../../../administration/raketasks/project_import_export.md) raketask.
-> - The exports are stored in a temporary [shared directory](../../../development/shared_files.md)
-> and are deleted every 24 hours by a specific worker.
-> - Group members will get exported as project members, as long as the user has
-> maintainer or admin access to the group where the exported project lives. An admin
-> in the import side is required to map the users, based on email or username.
-> Otherwise, a supplementary comment is left to mention the original author and
-> the MRs, notes or issues will be owned by the importer.
-> - Project members with owner access will get imported as maintainers.
-> - Control project Import/Export with the [API](../../../api/project_import_export.md).
-> - If an imported project contains merge requests originated from forks,
-> then new branches associated with such merge requests will be created
-> within a project during the import/export. Thus, the number of branches
-> in the exported project could be bigger than in the original project.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/3050) in GitLab 8.9.
+> - Importing will not be possible if the import instance version differs from
+> that of the exporter.
+> - For GitLab admins, please read through
+> [Project import/export administration](../../../administration/raketasks/project_import_export.md).
+> - For existing installations, the project import option has to be enabled in
+> application settings (`/admin/application_settings`) under 'Import sources'.
+> Ask your administrator if you don't see the **GitLab export** button when
+> creating a new project.
+> - Starting with GitLab 10.0, administrators can disable the project export option
+> on the GitLab instance in application settings (`/admin/application_settings`)
+> under 'Visibility and Access Controls'.
+> - You can find some useful raketasks if you are an administrator in the
+> [import_export](../../../administration/raketasks/project_import_export.md) raketask.
+> - The exports are stored in a temporary [shared directory](../../../development/shared_files.md)
+> and are deleted every 24 hours by a specific worker.
+> - Group members will get exported as project members, as long as the user has
+> maintainer or admin access to the group where the exported project lives. An admin
+> in the import side is required to map the users, based on email or username.
+> Otherwise, a supplementary comment is left to mention the original author and
+> the MRs, notes or issues will be owned by the importer.
+> - Project members with owner access will get imported as maintainers.
+> - Control project Import/Export with the [API](../../../api/project_import_export.md).
+> - If an imported project contains merge requests originated from forks,
+> then new branches associated with such merge requests will be created
+> within a project during the import/export. Thus, the number of branches
+> in the exported project could be bigger than in the original project.
Existing projects running on any GitLab instance or GitLab.com can be exported
with all their related data and be moved into a new GitLab instance.
@@ -74,6 +74,13 @@ The following items will NOT be exported:
- CI variables
- Webhooks
- Any encrypted tokens
+- Merge Request Approvers
+- Push Rules
+- Awards
+
+NOTE: **Note:**
+For more details on the specific data persisted in a project export, see the
+[`import_export.yml`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/import_export/import_export.yml) file.
## Exporting a project and its data
@@ -83,29 +90,34 @@ The following items will NOT be exported:
1. Scroll down to find the **Export project** button:
- ![Export button](img/import_export_export_button.png)
+ ![Export button](img/import_export_export_button.png)
1. Once the export is generated, you should receive an e-mail with a link to
download the file:
- ![Email download link](img/import_export_mail_link.png)
+ ![Email download link](img/import_export_mail_link.png)
1. Alternatively, you can come back to the project settings and download the
file from there, or generate a new export. Once the file available, the page
should show the **Download export** button:
- ![Download export](img/import_export_download_export.png)
+ ![Download export](img/import_export_download_export.png)
## Importing the project
-1. The GitLab project import feature is the first import option when creating a
+1. The GitLab project import feature is the first import option when creating a
new project. Click on **GitLab export**:
- ![New project](img/import_export_new_project.png)
+ ![New project](img/import_export_new_project.png)
1. Enter your project name and URL. Then select the file you exported previously:
- ![Select file](img/import_export_select_file.png)
+ ![Select file](img/import_export_select_file.png)
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 06b431decad..17ec9ecb5d1 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -44,7 +44,7 @@ Set up your project's merge request settings:
- Merge request [description templates](../description_templates.md#description-templates).
- Enable [merge request approvals](../merge_requests/merge_request_approvals.md). **(STARTER)**
- Enable [merge only of pipeline succeeds](../merge_requests/merge_when_pipeline_succeeds.md).
-- Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-discussions-are-resolved).
+- Enable [merge only when all discussions are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved).
![project's merge request settings](img/merge_requests_settings.png)
@@ -96,7 +96,7 @@ To rename a repository:
1. Hit **Rename project**.
Remember that this can have unintended side effects since everyone with the
-old URL will not be able to push or pull. Read more about what happens with the
+old URL will not be able to push or pull. Read more about what happens with the
[redirects when renaming repositories](../index.md#redirects-when-changing-repository-paths).
#### Transferring an existing project into another namespace
diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md
index 3d92508ad04..9bf400e7dff 100644
--- a/doc/user/project/web_ide/index.md
+++ b/doc/user/project/web_ide/index.md
@@ -295,6 +295,5 @@ active terminal at a time.
connect to the runner. Please try to stop and restart the terminal. If the
problem persists, double check your runner configuration.
-
[ce]: https://about.gitlab.com/pricing/
[ee]: https://about.gitlab.com/pricing/
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/README.md b/doc/workflow/README.md
index 6ad61932868..c6396672e59 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -38,7 +38,7 @@ comments: false
- [Authorization for merge requests](../user/project/merge_requests/authorization_for_merge_requests.md)
- [Cherry-pick changes](../user/project/merge_requests/cherry_pick_changes.md)
- [Merge when pipeline succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md)
- - [Resolve discussion comments in merge requests reviews](../user/discussions/index.md)
+ - [Resolve threads in merge requests reviews](../user/discussions/index.md)
- [Resolve merge conflicts in the UI](../user/project/merge_requests/resolve_conflicts.md)
- [Revert changes in the UI](../user/project/merge_requests/revert_changes.md)
- [Merge requests versions](../user/project/merge_requests/versions.md)
diff --git a/doc/workflow/file_finder.md b/doc/workflow/file_finder.md
index 8d87b030c83..52c83caeea8 100644
--- a/doc/workflow/file_finder.md
+++ b/doc/workflow/file_finder.md
@@ -2,8 +2,6 @@
> [Introduced][gh-9889] in GitLab 8.4.
----
-
The file finder feature allows you to quickly shortcut your way when you are
searching for a file in a repository using the GitLab UI.
@@ -12,8 +10,6 @@ project.
![Find file button](img/file_finder_find_button.png)
----
-
For those who prefer to keep their fingers on the keyboard, there is a
[shortcut button](shortcuts.md) as well, which you can invoke from _anywhere_
in a project.
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/issue_weight.md b/doc/workflow/issue_weight.md
index afb623e1967..a80519f0748 100644
--- a/doc/workflow/issue_weight.md
+++ b/doc/workflow/issue_weight.md
@@ -9,7 +9,7 @@ value or complexity a given issue has or will cost.
You can set the weight of an issue during its creation, by simply changing the
value in the dropdown menu. You can set it to a non-negative integer
-value from 0, 1, 2, and so on. (The database stores a 4-byte value, so the
+value from 0, 1, 2, and so on. (The database stores a 4-byte value, so the
upper bound is essentially limitless).
You can remove weight from an issue
as well.
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 03af8fad759..3160b0c4deb 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -91,6 +91,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) |
@@ -148,20 +149,20 @@ On Omnibus installations, the settings are prefixed by `lfs_object_store_`:
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
the values you want:
- ```ruby
- gitlab_rails['lfs_object_store_enabled'] = true
- gitlab_rails['lfs_object_store_remote_directory'] = "lfs-objects"
- gitlab_rails['lfs_object_store_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'aws_access_key_id' => '1ABCD2EFGHI34JKLM567N',
- 'aws_secret_access_key' => 'abcdefhijklmnopQRSTUVwxyz0123456789ABCDE',
- # The below options configure an S3 compatible host instead of AWS
- 'host' => 'localhost',
- 'endpoint' => 'http://127.0.0.1:9000',
- 'path_style' => true
- }
- ```
+ ```ruby
+ gitlab_rails['lfs_object_store_enabled'] = true
+ gitlab_rails['lfs_object_store_remote_directory'] = "lfs-objects"
+ gitlab_rails['lfs_object_store_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'aws_access_key_id' => '1ABCD2EFGHI34JKLM567N',
+ 'aws_secret_access_key' => 'abcdefhijklmnopQRSTUVwxyz0123456789ABCDE',
+ # The below options configure an S3 compatible host instead of AWS
+ 'host' => 'localhost',
+ 'endpoint' => 'http://127.0.0.1:9000',
+ 'path_style' => true
+ }
+ ```
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:
@@ -182,22 +183,22 @@ For source installations the settings are nested under `lfs:` and then
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
- ```yaml
- lfs:
- enabled: true
- object_store:
- enabled: false
- remote_directory: lfs-objects # Bucket name
- connection:
- provider: AWS
- aws_access_key_id: 1ABCD2EFGHI34JKLM567N
- aws_secret_access_key: abcdefhijklmnopQRSTUVwxyz0123456789ABCDE
- region: eu-central-1
- # Use the following options to configure an AWS compatible host such as Minio
- host: 'localhost'
- endpoint: 'http://127.0.0.1:9000'
- path_style: true
- ```
+ ```yaml
+ lfs:
+ enabled: true
+ object_store:
+ enabled: false
+ remote_directory: lfs-objects # Bucket name
+ connection:
+ provider: AWS
+ aws_access_key_id: 1ABCD2EFGHI34JKLM567N
+ aws_secret_access_key: abcdefhijklmnopQRSTUVwxyz0123456789ABCDE
+ region: eu-central-1
+ # Use the following options to configure an AWS compatible host such as Minio
+ host: 'localhost'
+ endpoint: 'http://127.0.0.1:9000'
+ path_style: true
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Migrate any existing local LFS objects to the object storage:
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 202f2e39975..b6bba57049d 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -250,7 +250,7 @@ If you are storing LFS files outside of GitLab you can disable LFS on the projec
It is possible to host LFS objects externally by setting a custom LFS url with `git config -f .lfsconfig lfs.url https://example.com/<project>.git/info/lfs`.
-You might choose to do this if you are using an appliance like a Sonatype Nexus to store LFS data. If you choose to use an external LFS store,
+You might choose to do this if you are using an appliance like a Sonatype Nexus to store LFS data. If you choose to use an external LFS store,
GitLab will not be able to verify LFS objects which means that pushes will fail if you have GitLab LFS support enabled.
To stop push failure, LFS support can be disabled in the [Project settings](../../user/project/settings/index.md). This means you will lose GitLab LFS value-adds (Verifying LFS objects, UI integration for LFS).
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 1e8674f863d..d82f7c6fdc7 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -138,7 +138,7 @@ Notification emails include headers that provide extra content about the notific
| X-GitLab-Project-Id | The ID of the project |
| X-GitLab-Project-Path | The path of the project |
| X-GitLab-(Resource)-ID | The ID of the resource the notification is for, where resource is `Issue`, `MergeRequest`, `Commit`, etc|
-| X-GitLab-Discussion-ID | Only in comment emails, the ID of the discussion the comment is from |
+| X-GitLab-Discussion-ID | Only in comment emails, the ID of the thread the comment is from |
| X-GitLab-Pipeline-Id | Only in pipeline emails, the ID of the pipeline the notification is for |
| X-GitLab-Reply-Key | A unique token to support reply by email |
| X-GitLab-NotificationReason | The reason for being notified. "mentioned", "assigned", etc |
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 87ca46e94be..0b8e7d851b0 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -92,9 +92,9 @@ 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)**
@@ -118,9 +118,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)
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index 4286a3625a2..b55c6b2e3df 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -75,7 +75,7 @@ Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
### Limit displayed units to hours
-> Introduced in GitLab 12.0.
+> Introduced in GitLab 12.1.
The display of time units can be limited to hours through the option in **Admin Area > Settings > Preferences** under 'Localization'.
diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md
index da51c0f2c93..60a4d0f19de 100644
--- a/doc/workflow/timezone.md
+++ b/doc/workflow/timezone.md
@@ -2,13 +2,12 @@
The global time zone configuration parameter can be changed in `config/gitlab.yml`:
-```
+```text
# time_zone: 'UTC'
```
Uncomment and customize if you want to change the default time zone of the GitLab application.
-
## Viewing available timezones
To see all available time zones, run `bundle exec rake time:zones:all`.
@@ -26,14 +25,13 @@ To obtain a list of timezones, log in to your GitLab application server and run
To update, add the timezone that best applies to your location. For example:
-```
+```ruby
gitlab_rails['time_zone'] = 'America/New_York'
```
After adding the configuration parameter, reconfigure and restart your GitLab instance:
-```
+```sh
gitlab-ctl reconfigure
gitlab-ctl restart
```
-
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 1f8900c734b..0432c406f31 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -12,8 +12,6 @@ in a simple dashboard.
![To Do screenshot showing a list of items to check on](img/todos_index.png)
----
-
You can quickly access your To-Do List by clicking the checkmark icon next to the
search bar in the top navigation. If the count is:
@@ -31,15 +29,15 @@ A To Do displays on your To-Do List when:
- An issue or merge request is assigned to you
- You are `@mentioned` in the description or comment of an:
- - Issue
- - Merge Request
- - Epic **(ULTIMATE)**
+ - Issue
+ - Merge Request
+ - Epic **(ULTIMATE)**
- 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
- - Have set it to automatically merge once the pipeline succeeds
+ - 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).
@@ -94,8 +92,6 @@ Actions that dismiss To-Do items include:
- Adding/removing a label
- Commenting on the issue
----
-
Your To-Do List is personal, and items are only marked as done if the action comes from
you. If you close the issue or merge request, your To Do is automatically
marked as done.
@@ -108,8 +104,6 @@ To prevent other users from closing issues without you being notified, if someon
There is just one To Do for each of these, so mentioning a user a hundred times in an issue will only trigger one To Do.
----
-
If no action is needed, you can manually mark the To Do as done by clicking the
corresponding **Done** button, and it will disappear from your To-Do List.
diff --git a/jest.config.js b/jest.config.js
index 84481642250..e4ac71a1a17 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -15,9 +15,18 @@ if (process.env.CI) {
]);
}
+let testMatch = ['<rootDir>/spec/frontend/**/*_spec.js', '<rootDir>/ee/spec/frontend/**/*_spec.js'];
+
+// workaround for eslint-import-resolver-jest only resolving in test files
+// see https://github.com/JoinColony/eslint-import-resolver-jest#note
+const isESLint = module.parent.filename.includes('/eslint-import-resolver-jest/');
+if (isESLint) {
+ testMatch = testMatch.map(path => path.replace('_spec.js', ''));
+}
+
// eslint-disable-next-line import/no-commonjs
module.exports = {
- testMatch: ['<rootDir>/spec/frontend/**/*_spec.js', '<rootDir>/ee/spec/frontend/**/*_spec.js'],
+ testMatch,
moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper: {
'^~(/.*)$': '<rootDir>/app/assets/javascripts$1',
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 42499c5b41e..223ae13bd2d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -10,13 +10,14 @@ module API
NAMESPACE_OR_PROJECT_REQUIREMENTS = { id: NO_SLASH_URL_PART_REGEX }.freeze
COMMIT_ENDPOINT_REQUIREMENTS = NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(sha: NO_SLASH_URL_PART_REGEX).freeze
USER_REQUIREMENTS = { user_id: NO_SLASH_URL_PART_REGEX }.freeze
+ LOG_FILTERS = ::Rails.application.config.filter_parameters + [/^output$/]
insert_before Grape::Middleware::Error,
GrapeLogging::Middleware::RequestLogger,
logger: Logger.new(LOG_FILENAME),
formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new,
include: [
- GrapeLogging::Loggers::FilterParameters.new,
+ GrapeLogging::Loggers::FilterParameters.new(LOG_FILTERS),
GrapeLogging::Loggers::ClientEnv.new,
Gitlab::GrapeLogging::Loggers::RouteLogger.new,
Gitlab::GrapeLogging::Loggers::UserLogger.new,
@@ -111,6 +112,7 @@ module API
mount ::API::Features
mount ::API::Files
mount ::API::GroupBoards
+ mount ::API::GroupClusters
mount ::API::GroupLabels
mount ::API::GroupMilestones
mount ::API::Groups
@@ -166,6 +168,7 @@ module API
mount ::API::Templates
mount ::API::Todos
mount ::API::Triggers
+ mount ::API::UserCounts
mount ::API::Users
mount ::API::Variables
mount ::API::Version
diff --git a/lib/api/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 eebded87ebc..e4f4e79cd46 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -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::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/entities.rb b/lib/api/entities.rb
index b9aa387ba61..10b4f8934d7 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -201,6 +201,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
projects_relation.preload(:project_feature, :route)
.preload(:import_state, :tags)
+ .preload(:auto_devops)
.preload(namespace: [:route, :owner])
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -247,12 +248,20 @@ module API
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
+ # TODO: remove in API v5, replaced by *_access_level
expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
+ expose(:issues_access_level) { |project, options| project.project_feature.string_access_level(:issues) }
+ expose(:repository_access_level) { |project, options| project.project_feature.string_access_level(:repository) }
+ expose(:merge_requests_access_level) { |project, options| project.project_feature.string_access_level(:merge_requests) }
+ expose(:wiki_access_level) { |project, options| project.project_feature.string_access_level(:wiki) }
+ expose(:builds_access_level) { |project, options| project.project_feature.string_access_level(:builds) }
+ expose(:snippets_access_level) { |project, options| project.project_feature.string_access_level(:snippets) }
+
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
@@ -267,6 +276,12 @@ module API
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :ci_default_git_depth
expose :public_builds, as: :public_jobs
+ expose :build_git_strategy, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options|
+ project.build_allow_git_fetch ? 'fetch' : 'clone'
+ end
+ expose :build_timeout
+ expose :auto_cancel_pending_pipelines
+ expose :build_coverage_regex
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links, options)
@@ -279,7 +294,10 @@ module API
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
- expose :external_authorization_classification_label
+ expose :auto_devops_enabled?, as: :auto_devops_enabled
+ expose :auto_devops_deploy_strategy do |project, options|
+ project.auto_devops.nil? ? 'continuous' : project.auto_devops.deploy_strategy
+ end
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {})
@@ -289,6 +307,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
super(projects_relation).preload(:group)
.preload(:ci_cd_settings)
+ .preload(:auto_devops)
.preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,
@@ -491,7 +510,7 @@ module API
end
end
- class ProjectEntity < Grape::Entity
+ class IssuableEntity < Grape::Entity
expose :id, :iid
expose(:project_id) { |entity| entity&.project.try(:id) }
expose :title, :description
@@ -544,7 +563,7 @@ module API
end
end
- class IssueBasic < ProjectEntity
+ class IssueBasic < IssuableEntity
expose :closed_at
expose :closed_by, using: Entities::UserBasic
@@ -650,14 +669,14 @@ module API
end
end
- class MergeRequestSimple < ProjectEntity
+ class MergeRequestSimple < IssuableEntity
expose :title
expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request)
end
end
- class MergeRequestBasic < ProjectEntity
+ class MergeRequestBasic < IssuableEntity
expose :merged_by, using: Entities::UserBasic do |merge_request, _options|
merge_request.metrics&.merged_by
end
@@ -1033,15 +1052,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
@@ -1666,5 +1678,9 @@ module API
class ClusterProject < Cluster
expose :project, using: Entities::BasicProjectDetails
end
+
+ class ClusterGroup < Cluster
+ expose :group, using: Entities::BasicGroupDetails
+ end
end
end
diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb
new file mode 100644
index 00000000000..db0f8081140
--- /dev/null
+++ b/lib/api/group_clusters.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+module API
+ class GroupClusters < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ # EE::API::GroupClusters will
+ # override these methods
+ helpers do
+ params :create_params_ee do
+ end
+
+ params :update_params_ee do
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of the group'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all clusters from the group' do
+ success Entities::Cluster
+ end
+ params do
+ use :pagination
+ end
+ get ':id/clusters' do
+ authorize! :read_cluster, user_group
+
+ present paginate(clusters_for_current_user), with: Entities::Cluster
+ end
+
+ desc 'Get specific cluster for the group' do
+ success Entities::ClusterGroup
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The cluster ID'
+ end
+ get ':id/clusters/:cluster_id' do
+ authorize! :read_cluster, cluster
+
+ present cluster, with: Entities::ClusterGroup
+ end
+
+ desc 'Adds an existing cluster' do
+ success Entities::ClusterGroup
+ end
+ params do
+ requires :name, type: String, desc: 'Cluster name'
+ optional :enabled, type: Boolean, default: true, desc: 'Determines if cluster is active or not, defaults to true'
+ optional :domain, type: String, desc: 'Cluster base domain'
+ optional :managed, type: Boolean, default: true, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster, defaults to true'
+ requires :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
+ requires :api_url, type: String, allow_blank: false, desc: 'URL to access the Kubernetes API'
+ requires :token, type: String, desc: 'Token to authenticate against Kubernetes'
+ optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
+ optional :namespace, type: String, desc: 'Unique namespace related to Group'
+ optional :authorization_type, type: String, values: Clusters::Platforms::Kubernetes.authorization_types.keys, default: 'rbac', desc: 'Cluster authorization type, defaults to RBAC'
+ end
+ use :create_params_ee
+ end
+ post ':id/clusters/user' do
+ authorize! :add_cluster, user_group
+
+ user_cluster = ::Clusters::CreateService
+ .new(current_user, create_cluster_user_params)
+ .execute
+
+ if user_cluster.persisted?
+ present user_cluster, with: Entities::ClusterGroup
+ else
+ render_validation_error!(user_cluster)
+ end
+ end
+
+ desc 'Update an existing cluster' do
+ success Entities::ClusterGroup
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The cluster ID'
+ optional :name, type: String, desc: 'Cluster name'
+ optional :domain, type: String, desc: 'Cluster base domain'
+ optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do
+ optional :api_url, type: String, desc: 'URL to access the Kubernetes API'
+ optional :token, type: String, desc: 'Token to authenticate against Kubernetes'
+ optional :ca_cert, type: String, desc: 'TLS certificate (needed if API is using a self-signed TLS certificate)'
+ optional :namespace, type: String, desc: 'Unique namespace related to Group'
+ end
+ use :update_params_ee
+ end
+ put ':id/clusters/:cluster_id' do
+ authorize! :update_cluster, cluster
+
+ update_service = Clusters::UpdateService.new(current_user, update_cluster_params)
+
+ if update_service.execute(cluster)
+ present cluster, with: Entities::ClusterGroup
+ else
+ render_validation_error!(cluster)
+ end
+ end
+
+ desc 'Remove a cluster' do
+ success Entities::ClusterGroup
+ end
+ params do
+ requires :cluster_id, type: Integer, desc: 'The Cluster ID'
+ end
+ delete ':id/clusters/:cluster_id' do
+ authorize! :admin_cluster, cluster
+
+ destroy_conditionally!(cluster)
+ end
+ end
+
+ helpers do
+ def clusters_for_current_user
+ @clusters_for_current_user ||= ClustersFinder.new(user_group, current_user, :all).execute
+ end
+
+ def cluster
+ @cluster ||= clusters_for_current_user.find(params[:cluster_id])
+ end
+
+ def create_cluster_user_params
+ declared_params.merge({
+ provider_type: :user,
+ platform_type: :kubernetes,
+ clusterable: user_group
+ })
+ end
+
+ def update_cluster_params
+ declared_params(include_missing: false).without(:cluster_id)
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb
index 94010ab1bc2..bd60470fbd6 100644
--- a/lib/api/helpers/graphql_helpers.rb
+++ b/lib/api/helpers/graphql_helpers.rb
@@ -7,8 +7,6 @@ module API
# should be in app/graphql/ or lib/gitlab/graphql/
module GraphqlHelpers
def conditionally_graphql!(fallback:, query:, context: {}, transform: nil)
- return fallback.call unless Feature.enabled?(:graphql)
-
result = GitlabSchema.execute(query, context: context)
if transform
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index c318f5b9127..9afe6c5b027 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -72,7 +72,7 @@ module API
result == 'PONG'
rescue => e
- Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}")
+ Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
false
end
diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb
index 2a9b17ad22a..71bbc218f94 100644
--- a/lib/api/helpers/pagination.rb
+++ b/lib/api/helpers/pagination.rb
@@ -205,7 +205,9 @@ module API
limited_total_count = pagination_data.total_count_with_limit
if limited_total_count > Kaminari::ActiveRecordRelationMethods::MAX_COUNT_LIMIT
- pagination_data.without_count
+ # The call to `total_count_with_limit` memoizes `@arel` because of a call to `references_eager_loaded_tables?`
+ # We need to call `reset` because `without_count` relies on `@arel` being unmemoized
+ pagination_data.reset.without_count
else
pagination_data
end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index f242f1fea0e..51b7cf05c8f 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -8,12 +8,26 @@ module API
params :optional_project_params_ce do
optional :description, type: String, desc: 'The description of the project'
+ optional :build_git_strategy, type: String, values: %w(fetch clone), desc: 'The Git strategy. Defaults to `fetch`'
+ optional :build_timeout, type: Integer, desc: 'Build timeout'
+ optional :auto_cancel_pending_pipelines, type: String, values: %w(disabled enabled), desc: 'Auto-cancel pending pipelines'
+ optional :build_coverage_regex, type: String, desc: 'Test coverage parsing'
optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
+
+ # TODO: remove in API v5, replaced by *_access_level
optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
+
+ optional :issues_access_level, type: String, values: %w(disabled private enabled), desc: 'Issues access level. One of `disabled`, `private` or `enabled`'
+ optional :repository_access_level, type: String, values: %w(disabled private enabled), desc: 'Repository access level. One of `disabled`, `private` or `enabled`'
+ optional :merge_requests_access_level, type: String, values: %w(disabled private enabled), desc: 'Merge requests access level. One of `disabled`, `private` or `enabled`'
+ optional :wiki_access_level, type: String, values: %w(disabled private enabled), desc: 'Wiki access level. One of `disabled`, `private` or `enabled`'
+ optional :builds_access_level, type: String, values: %w(disabled private enabled), desc: 'Builds access level. One of `disabled`, `private` or `enabled`'
+ optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`'
+
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
@@ -28,8 +42,9 @@ module API
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
- optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning'
+ optional :auto_devops_enabled, type: Boolean, desc: 'Flag indication if Auto DevOps is enabled'
+ optional :auto_devops_deploy_strategy, type: String, values: %w(continuous manual timed_incremental), desc: 'Auto Deploy strategy'
end
params :optional_project_params_ee do
@@ -48,15 +63,21 @@ module API
def self.update_params_at_least_one_of
[
- :jobs_enabled,
- :resolve_outdated_diff_discussions,
+ :auto_devops_enabled,
+ :auto_devops_deploy_strategy,
+ :auto_cancel_pending_pipelines,
+ :build_coverage_regex,
+ :build_git_strategy,
+ :build_timeout,
+ :builds_access_level,
:ci_config_path,
+ :ci_default_git_depth,
:container_registry_enabled,
:default_branch,
:description,
- :issues_enabled,
+ :issues_access_level,
:lfs_enabled,
- :merge_requests_enabled,
+ :merge_requests_access_level,
:merge_method,
:name,
:only_allow_merge_if_all_discussions_are_resolved,
@@ -64,16 +85,28 @@ module API
:path,
:printing_merge_request_link_enabled,
:public_builds,
+ :repository_access_level,
:request_access_enabled,
+ :resolve_outdated_diff_discussions,
:shared_runners_enabled,
- :snippets_enabled,
+ :snippets_access_level,
:tag_list,
:visibility,
- :wiki_enabled,
+ :wiki_access_level,
:avatar,
- :external_authorization_classification_label
+
+ # TODO: remove in API v5, replaced by *_access_level
+ :issues_enabled,
+ :jobs_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :jobs_enabled,
+ :snippets_enabled
]
end
+
+ def filter_attributes_using_license!(attrs)
+ end
end
end
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index ff73a49d5e8..100463fcb95 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -7,8 +7,7 @@ module API
JOB_TOKEN_PARAM = :token
def runner_registration_token_valid?
- ActiveSupport::SecurityUtils.variable_size_secure_compare(params[:token],
- Gitlab::CurrentSettings.runners_registration_token)
+ ActiveSupport::SecurityUtils.secure_compare(params[:token], Gitlab::CurrentSettings.runners_registration_token)
end
def authenticate_runner!
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index e7504051808..21d4928193e 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -28,7 +28,7 @@ module API
desc 'Import a GitHub project' do
detail 'This feature was introduced in GitLab 11.3.4.'
- success Entities::ProjectEntity
+ success ::ProjectEntity
end
params do
requires :personal_access_token, type: String, desc: 'GitHub personal access token'
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
index dcc8d94fb79..4f093e9be08 100644
--- a/lib/api/project_clusters.rb
+++ b/lib/api/project_clusters.rb
@@ -65,7 +65,7 @@ module API
use :create_params_ee
end
post ':id/clusters/user' do
- authorize! :add_cluster, user_project, 'Instance does not support multiple Kubernetes clusters'
+ authorize! :add_cluster, user_project
user_cluster = ::Clusters::CreateService
.new(current_user, create_cluster_user_params)
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index a7d62014509..0923d31f5ff 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -145,6 +145,7 @@ module API
post do
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
+ filter_attributes_using_license!(attrs)
project = ::Projects::CreateService.new(current_user, attrs).execute
if project.saved?
@@ -179,6 +180,7 @@ module API
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
+ filter_attributes_using_license!(attrs)
project = ::Projects::CreateService.new(user, attrs).execute
if project.saved?
@@ -292,7 +294,7 @@ module API
authorize! :change_visibility_level, user_project if attrs[:visibility].present?
attrs = translate_params_for_compatibility(attrs)
-
+ filter_attributes_using_license!(attrs)
verify_update_project_attrs!(user_project, attrs)
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
diff --git a/lib/api/releases.rb b/lib/api/releases.rb
index fdd8406388e..7a3d804c30c 100644
--- a/lib/api/releases.rb
+++ b/lib/api/releases.rb
@@ -78,7 +78,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag', as: :tag
optional :name, type: String, desc: 'The name of the release'
optional :description, type: String, desc: 'Release notes with markdown support'
- optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.'
+ optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.'
end
put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do
authorize_update_release!
diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb
new file mode 100644
index 00000000000..8df4b381bbf
--- /dev/null
+++ b/lib/api/user_counts.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module API
+ class UserCounts < Grape::API
+ resource :user_counts do
+ desc 'Return the user specific counts' do
+ detail 'Open MR Count'
+ end
+ get do
+ unauthorized! unless current_user
+
+ {
+ merge_requests: current_user.assigned_open_merge_requests_count
+ }
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 41418aa216c..a4ac5b629b8 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -51,7 +51,7 @@ module API
optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
optional :avatar, type: File, desc: 'Avatar image for user'
- optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
+ optional :private_profile, type: Boolean, default: false, desc: 'Flag indicating the user has a private profile'
all_or_none_of :extern_uid, :provider
use :optional_params_ee
@@ -148,7 +148,7 @@ module API
end
desc 'Create a user. Available only for admins.' do
- success Entities::UserPublic
+ success Entities::UserWithAdmin
end
params do
requires :email, type: String, desc: 'The email of the user'
@@ -168,7 +168,7 @@ module API
user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true)
if user.persisted?
- present user, with: Entities::UserPublic, current_user: current_user
+ present user, with: Entities::UserWithAdmin, current_user: current_user
else
conflict!('Email has already been taken') if User
.by_any_email(user.email.downcase)
@@ -183,7 +183,7 @@ module API
end
desc 'Update a user. Available only for admins.' do
- success Entities::UserPublic
+ success Entities::UserWithAdmin
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
@@ -215,7 +215,7 @@ module API
result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute
if result[:status] == :success
- present user, with: Entities::UserPublic, current_user: current_user
+ present user, with: Entities::UserWithAdmin, current_user: current_user
else
render_validation_error!(user)
end
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/ascii_doc_sanitization_filter.rb b/lib/banzai/filter/ascii_doc_sanitization_filter.rb
new file mode 100644
index 00000000000..9105e86ad04
--- /dev/null
+++ b/lib/banzai/filter/ascii_doc_sanitization_filter.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # Sanitize HTML produced by AsciiDoc/Asciidoctor.
+ #
+ # Extends Banzai::Filter::BaseSanitizationFilter with specific rules.
+ class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter
+ # Section anchor link pattern
+ SECTION_LINK_REF_PATTERN = /\A#{Gitlab::Asciidoc::DEFAULT_ADOC_ATTRS['idprefix']}(:?[[:alnum:]]|-|_)+\z/.freeze
+ SECTION_HEADINGS = %w(h2 h3 h4 h5 h6).freeze
+
+ # Footnote link patterns
+ FOOTNOTE_LINK_ID_PATTERNS = {
+ a: /\A_footnoteref_\d+\z/,
+ div: /\A_footnotedef_\d+\z/
+ }.freeze
+
+ # Classes used by Asciidoctor to style components
+ ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze
+ CALLOUT_CLASSES = ['conum'].freeze
+ CHECKLIST_CLASSES = %w(fa fa-check-square-o fa-square-o).freeze
+ LIST_CLASSES = %w(checklist none no-bullet unnumbered unstyled).freeze
+
+ ELEMENT_CLASSES_WHITELIST = {
+ span: %w(big small underline overline line-through).freeze,
+ div: ['admonitionblock'].freeze,
+ td: ['icon'].freeze,
+ i: ADMONITION_CLASSES + CALLOUT_CLASSES + CHECKLIST_CLASSES,
+ ul: LIST_CLASSES,
+ ol: LIST_CLASSES,
+ a: ['anchor'].freeze
+ }.freeze
+
+ def customize_whitelist(whitelist)
+ # Allow marks
+ whitelist[:elements].push('mark')
+
+ # Allow any classes in `span`, `i`, `div`, `td`, `ul`, `ol` and `a` elements
+ # but then remove any unknown classes
+ whitelist[:attributes]['span'] = %w(class)
+ whitelist[:attributes]['div'].push('class')
+ whitelist[:attributes]['td'] = %w(class)
+ whitelist[:attributes]['i'] = %w(class)
+ whitelist[:attributes]['ul'] = %w(class)
+ whitelist[:attributes]['ol'] = %w(class)
+ whitelist[:attributes]['a'].push('class')
+ whitelist[:transformers].push(self.class.remove_element_classes)
+
+ # Allow `id` in heading elements for section anchors
+ SECTION_HEADINGS.each do |header|
+ whitelist[:attributes][header] = %w(id)
+ end
+ whitelist[:transformers].push(self.class.remove_non_heading_ids)
+
+ # Allow `id` in footnote elements
+ FOOTNOTE_LINK_ID_PATTERNS.keys.each do |element|
+ whitelist[:attributes][element.to_s].push('id')
+ end
+ whitelist[:transformers].push(self.class.remove_non_footnote_ids)
+
+ whitelist
+ end
+
+ class << self
+ def remove_non_footnote_ids
+ lambda do |env|
+ node = env[:node]
+
+ return unless (pattern = FOOTNOTE_LINK_ID_PATTERNS[node.name.to_sym])
+ return unless node.has_attribute?('id')
+
+ return if node['id'] =~ pattern
+
+ node.remove_attribute('id')
+ end
+ end
+
+ def remove_non_heading_ids
+ lambda do |env|
+ node = env[:node]
+
+ return unless SECTION_HEADINGS.any?(node.name)
+ return unless node.has_attribute?('id')
+
+ return if node['id'] =~ SECTION_LINK_REF_PATTERN
+
+ node.remove_attribute('id')
+ end
+ end
+
+ def remove_element_classes
+ lambda do |env|
+ node = env[:node]
+
+ return unless (classes_whitelist = ELEMENT_CLASSES_WHITELIST[node.name.to_sym])
+ return unless node.has_attribute?('class')
+
+ classes = node['class'].strip.split(' ')
+ allowed_classes = (classes & classes_whitelist)
+ if allowed_classes.empty?
+ node.remove_attribute('class')
+ else
+ node['class'] = allowed_classes.join(' ')
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/base_sanitization_filter.rb b/lib/banzai/filter/base_sanitization_filter.rb
new file mode 100644
index 00000000000..420e92cb1e8
--- /dev/null
+++ b/lib/banzai/filter/base_sanitization_filter.rb
@@ -0,0 +1,96 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # Sanitize HTML produced by markup languages (Markdown, AsciiDoc...).
+ # Specific rules are implemented in dedicated filters:
+ #
+ # - Banzai::Filter::SanitizationFilter (Markdown)
+ # - Banzai::Filter::AsciiDocSanitizationFilter (AsciiDoc/Asciidoctor)
+ #
+ # Extends HTML::Pipeline::SanitizationFilter with common rules.
+ class BaseSanitizationFilter < HTML::Pipeline::SanitizationFilter
+ include Gitlab::Utils::StrongMemoize
+
+ UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
+
+ def whitelist
+ strong_memoize(:whitelist) do
+ whitelist = super.deep_dup
+
+ # Allow span elements
+ whitelist[:elements].push('span')
+
+ # Allow data-math-style attribute in order to support LaTeX formatting
+ whitelist[:attributes]['code'] = %w(data-math-style)
+ whitelist[:attributes]['pre'] = %w(data-math-style)
+
+ # Allow html5 details/summary elements
+ whitelist[:elements].push('details')
+ whitelist[:elements].push('summary')
+
+ # Allow abbr elements with title attribute
+ whitelist[:elements].push('abbr')
+ whitelist[:attributes]['abbr'] = %w(title)
+
+ # Disallow `name` attribute globally, allow on `a`
+ whitelist[:attributes][:all].delete('name')
+ whitelist[:attributes]['a'].push('name')
+
+ # 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)
+
+ # Remove `rel` attribute from `a` elements
+ whitelist[:transformers].push(self.class.remove_rel)
+
+ customize_whitelist(whitelist)
+ end
+ end
+
+ def customize_whitelist(whitelist)
+ raise NotImplementedError
+ 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'
+ env[:node].remove_attribute('rel')
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/inline_embeds_filter.rb b/lib/banzai/filter/inline_embeds_filter.rb
new file mode 100644
index 00000000000..97394fd8f82
--- /dev/null
+++ b/lib/banzai/filter/inline_embeds_filter.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that inserts a node for each occurence of
+ # a given link format. To transform references to DB
+ # resources in place, prefer to inherit from AbstractReferenceFilter.
+ class InlineEmbedsFilter < HTML::Pipeline::Filter
+ # Find every relevant link, create a new node based on
+ # the link, and insert this node after any html content
+ # surrounding the link.
+ def call
+ return doc unless Feature.enabled?(:gfm_embedded_metrics, context[:project])
+
+ doc.xpath(xpath_search).each do |node|
+ next unless element = element_to_embed(node)
+
+ # We want this to follow any surrounding content. For example,
+ # if a link is inline in a paragraph.
+ node.parent.children.last.add_next_sibling(element)
+ end
+
+ doc
+ end
+
+ # Implement in child class.
+ #
+ # Return a Nokogiri::XML::Element to embed in the
+ # markdown.
+ def create_element(params)
+ end
+
+ # Implement in child class unless overriding #embed_params
+ #
+ # Returns the regex pattern used to filter
+ # to only matching urls.
+ def link_pattern
+ end
+
+ # Returns the xpath query string used to select nodes
+ # from the html document on which the embed is based.
+ #
+ # Override to select nodes other than links.
+ def xpath_search
+ 'descendant-or-self::a[@href]'
+ end
+
+ # Creates a new element based on the parameters
+ # obtained from the target link
+ def element_to_embed(node)
+ return unless params = embed_params(node)
+
+ create_element(params)
+ end
+
+ # Returns a hash of named parameters based on the
+ # provided regex with string keys.
+ #
+ # Override to select nodes other than links.
+ def embed_params(node)
+ url = node['href']
+
+ link_pattern.match(url) { |m| m.named_captures }
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/inline_metrics_filter.rb b/lib/banzai/filter/inline_metrics_filter.rb
new file mode 100644
index 00000000000..0120cc37d6f
--- /dev/null
+++ b/lib/banzai/filter/inline_metrics_filter.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that inserts a placeholder element for each
+ # reference to a metrics dashboard.
+ class InlineMetricsFilter < Banzai::Filter::InlineEmbedsFilter
+ # Placeholder element for the frontend to use as an
+ # injection point for charts.
+ def create_element(params)
+ doc.document.create_element(
+ 'div',
+ class: 'js-render-metrics',
+ 'data-dashboard-url': metrics_dashboard_url(params)
+ )
+ end
+
+ # Endpoint FE should hit to collect the appropriate
+ # chart information
+ def metrics_dashboard_url(params)
+ Gitlab::Metrics::Dashboard::Url.build_dashboard_url(
+ params['namespace'],
+ params['project'],
+ params['environment'],
+ embedded: true
+ )
+ end
+
+ # Search params for selecting metrics links. A few
+ # simple checks is enough to boost performance without
+ # the cost of doing a full regex match.
+ def xpath_search
+ "descendant-or-self::a[contains(@href,'metrics') and \
+ starts-with(@href, '#{Gitlab.config.gitlab.url}')]"
+ end
+
+ # Regular expression matching metrics urls
+ def link_pattern
+ Gitlab::Metrics::Dashboard::Url.regex
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb
new file mode 100644
index 00000000000..ff91be2cbb7
--- /dev/null
+++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that removes embeded elements that the current user does
+ # not have permission to view.
+ class InlineMetricsRedactorFilter < HTML::Pipeline::Filter
+ include Gitlab::Utils::StrongMemoize
+
+ METRICS_CSS_CLASS = '.js-render-metrics'
+
+ # Finds all embeds based on the css class the FE
+ # uses to identify the embedded content, removing
+ # only unnecessary nodes.
+ def call
+ return doc unless Feature.enabled?(:gfm_embedded_metrics, context[:project])
+
+ nodes.each do |node|
+ path = paths_by_node[node]
+ user_has_access = user_access_by_path[path]
+
+ node.remove unless user_has_access
+ end
+
+ doc
+ end
+
+ private
+
+ def user
+ context[:current_user]
+ end
+
+ # Returns all nodes which the FE will identify as
+ # a metrics dashboard placeholder element
+ #
+ # @return [Nokogiri::XML::NodeSet]
+ def nodes
+ @nodes ||= doc.css(METRICS_CSS_CLASS)
+ end
+
+ # Maps a node to the full path of a project.
+ # Memoized so we only need to run the regex to get
+ # the project full path from the url once per node.
+ #
+ # @return [Hash<Nokogiri::XML::Node, String>]
+ def paths_by_node
+ strong_memoize(:paths_by_node) do
+ nodes.each_with_object({}) do |node, paths|
+ paths[node] = path_for_node(node)
+ end
+ end
+ end
+
+ # Gets a project's full_path from the dashboard url
+ # in the placeholder node. The FE will use the attr
+ # `data-dashboard-url`, so we want to check against that
+ # attribute directly in case a user has manually
+ # created a metrics element (rather than supporting
+ # an alternate attr in InlineMetricsFilter).
+ #
+ # @return [String]
+ def path_for_node(node)
+ url = node.attribute('data-dashboard-url').to_s
+
+ Gitlab::Metrics::Dashboard::Url.regex.match(url) do |m|
+ "#{$~[:namespace]}/#{$~[:project]}"
+ end
+ end
+
+ # Maps a project's full path to a Project object.
+ # Contains all of the Projects referenced in the
+ # metrics placeholder elements of the current document
+ #
+ # @return [Hash<String, Project>]
+ def projects_by_path
+ strong_memoize(:projects_by_path) do
+ Project.eager_load(:route, namespace: [:route])
+ .where_full_path_in(paths_by_node.values.uniq)
+ .index_by(&:full_path)
+ end
+ end
+
+ # Returns a mapping representing whether the current user
+ # has permission to view the metrics for the project.
+ # Determined in a batch
+ #
+ # @return [Hash<Project, Boolean>]
+ def user_access_by_path
+ strong_memoize(:user_access_by_path) do
+ projects_by_path.each_with_object({}) do |(path, project), access|
+ access[path] = Ability.allowed?(user, :read_environment, project)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/reference_redactor_filter.rb
index 1f091f594f8..485d3fd5fc7 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/reference_redactor_filter.rb
@@ -7,12 +7,12 @@ module Banzai
#
# Expected to be run in its own post-processing pipeline.
#
- class RedactorFilter < HTML::Pipeline::Filter
+ class ReferenceRedactorFilter < HTML::Pipeline::Filter
def call
unless context[:skip_redaction]
context = RenderContext.new(project, current_user)
- Redactor.new(context).redact([doc])
+ ReferenceRedactor.new(context).redact([doc])
end
doc
diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb
index a4a06eae7b7..f57e57890f8 100644
--- a/lib/banzai/filter/sanitization_filter.rb
+++ b/lib/banzai/filter/sanitization_filter.rb
@@ -2,23 +2,13 @@
module Banzai
module Filter
- # Sanitize HTML
+ # Sanitize HTML produced by Markdown.
#
- # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
- class SanitizationFilter < HTML::Pipeline::SanitizationFilter
- include Gitlab::Utils::StrongMemoize
-
- UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
+ # Extends Banzai::Filter::BaseSanitizationFilter with specific rules.
+ class SanitizationFilter < Banzai::Filter::BaseSanitizationFilter
+ # Styles used by Markdown for table alignment
TABLE_ALIGNMENT_PATTERN = /text-align: (?<alignment>center|left|right)/.freeze
- def whitelist
- strong_memoize(:whitelist) do
- customize_whitelist(super.deep_dup)
- end
- end
-
- private
-
def customize_whitelist(whitelist)
# Allow table alignment; we whitelist specific text-align values in a
# transformer below
@@ -26,36 +16,9 @@ module Banzai
whitelist[:attributes]['td'] = %w(style)
whitelist[:css] = { properties: ['text-align'] }
- # Allow span elements
- whitelist[:elements].push('span')
-
- # Allow data-math-style attribute in order to support LaTeX formatting
- whitelist[:attributes]['code'] = %w(data-math-style)
- whitelist[:attributes]['pre'] = %w(data-math-style)
-
- # Allow html5 details/summary elements
- whitelist[:elements].push('details')
- whitelist[:elements].push('summary')
-
- # Allow abbr elements with title attribute
- whitelist[:elements].push('abbr')
- whitelist[:attributes]['abbr'] = %w(title)
-
# Allow the 'data-sourcepos' from CommonMark on all elements
whitelist[:attributes][:all].push('data-sourcepos')
- # Disallow `name` attribute globally, allow on `a`
- whitelist[:attributes][:all].delete('name')
- whitelist[:attributes]['a'].push('name')
-
- # 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)
-
- # Remove `rel` attribute from `a` elements
- whitelist[:transformers].push(self.class.remove_rel)
-
# Remove any `style` properties not required for table alignment
whitelist[:transformers].push(self.class.remove_unsafe_table_style)
@@ -69,43 +32,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'
- env[:node].remove_attribute('rel')
- end
- end
- end
-
def remove_unsafe_table_style
lambda do |env|
node = env[:node]
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 75661ffa233..d6d29f4bfab 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -72,7 +72,7 @@ module Banzai
#
# Returns an Array containing the redacted documents.
def redact_documents(documents)
- redactor = Redactor.new(context)
+ redactor = ReferenceRedactor.new(context)
redactor.redact(documents)
end
diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb
index 6be489c6572..d25b74b23b2 100644
--- a/lib/banzai/pipeline/ascii_doc_pipeline.rb
+++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb
@@ -5,7 +5,7 @@ module Banzai
class AsciiDocPipeline < BasePipeline
def self.filters
FilterArray[
- Filter::SanitizationFilter,
+ Filter::AsciiDocSanitizationFilter,
Filter::SyntaxHighlightFilter,
Filter::ExternalLinkFilter,
Filter::PlantumlFilter,
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index d67f461be57..2c1006f708a 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -25,6 +25,7 @@ module Banzai
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
+ Filter::InlineMetricsFilter,
Filter::TableOfContentsFilter,
Filter::AutolinkFilter,
Filter::ExternalLinkFilter,
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index 7eaad6d7560..54af26b41be 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -12,7 +12,8 @@ module Banzai
def self.internal_link_filters
[
- Filter::RedactorFilter,
+ Filter::ReferenceRedactorFilter,
+ Filter::InlineMetricsRedactorFilter,
Filter::RelativeLinkFilter,
Filter::IssuableStateFilter,
Filter::SuggestionFilter
diff --git a/lib/banzai/redactor.rb b/lib/banzai/reference_redactor.rb
index c2da7fec7cc..eb5c35da375 100644
--- a/lib/banzai/redactor.rb
+++ b/lib/banzai/reference_redactor.rb
@@ -3,7 +3,7 @@
module Banzai
# Class for removing Markdown references a certain user is not allowed to
# view.
- class Redactor
+ class ReferenceRedactor
attr_reader :context
# context - An instance of `Banzai::RenderContext`.
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index c7239a5eaa6..3cb9ec21e8f 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -55,11 +55,16 @@ module Banzai
# Perform multiple render from an Array of Markdown String into an
# Array of HTML-safe String of HTML.
#
- # As the rendered Markdown String can be already cached read all the data
- # from the cache using Rails.cache.read_multi operation. If the Markdown String
- # is not in the cache or it's not cacheable (no cache_key entry is provided in
- # the context) the Markdown String is rendered and stored in the cache so the
- # next render call gets the rendered HTML-safe String from the cache.
+ # The redis cache is completely obviated if we receive a `:rendered` key in the
+ # context, as it is assumed the item has been pre-rendered somewhere else and there
+ # is no need to cache it.
+ #
+ # If no `:rendered` key is present in the context, as the rendered Markdown String
+ # can be already cached, read all the data from the cache using
+ # Rails.cache.read_multi operation. If the Markdown String is not in the cache
+ # or it's not cacheable (no cache_key entry is provided in the context) the
+ # Markdown String is rendered and stored in the cache so the next render call
+ # gets the rendered HTML-safe String from the cache.
#
# For further explanation see #render method comments.
#
@@ -76,19 +81,34 @@ module Banzai
# => [{ text: '### Hello',
# context: { cache_key: [note, :note] } }]
def self.cache_collection_render(texts_and_contexts)
- items_collection = texts_and_contexts.each_with_index do |item, index|
+ items_collection = texts_and_contexts.each do |item|
context = item[:context]
- cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline])
- item[:cache_key] = cache_key if cache_key
+ if context.key?(:rendered)
+ item[:rendered] = context.delete(:rendered)
+ else
+ # If the attribute didn't come in pre-rendered, let's prepare it for caching it in redis
+ cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline])
+ item[:cache_key] = cache_key if cache_key
+ end
end
- cacheable_items, non_cacheable_items = items_collection.partition { |item| item.key?(:cache_key) }
+ cacheable_items, non_cacheable_items = items_collection.group_by do |item|
+ if item.key?(:rendered)
+ # We're not really doing anything here as these don't need any processing, but leaving it just in case
+ # as they could have a cache_key and we don't want them to be re-rendered
+ :rendered
+ elsif item.key?(:cache_key)
+ :cacheable
+ else
+ :non_cacheable
+ end
+ end.values_at(:cacheable, :non_cacheable)
items_in_cache = []
items_not_in_cache = []
- unless cacheable_items.empty?
+ if cacheable_items.present?
items_in_cache = Rails.cache.read_multi(*cacheable_items.map { |item| item[:cache_key] })
items_not_in_cache = cacheable_items.reject do |item|
item[:rendered] = items_in_cache[item[:cache_key]]
@@ -96,7 +116,7 @@ module Banzai
end
end
- (items_not_in_cache + non_cacheable_items).each do |item|
+ (items_not_in_cache + Array.wrap(non_cacheable_items)).each do |item|
item[:rendered] = render(item[:text], item[:context])
Rails.cache.write(item[:cache_key], item[:rendered]) if item[:cache_key]
end
@@ -114,7 +134,7 @@ module Banzai
#
# This method is used to perform state-dependent changes to a String of
# HTML, such as removing references that the current user doesn't have
- # permission to make (`RedactorFilter`).
+ # permission to make (`ReferenceRedactorFilter`).
#
# html - String to process
# context - Hash of options to customize output
diff --git a/lib/feature/gitaly.rb b/lib/feature/gitaly.rb
index 67c0b902c0c..edfd2fb17f3 100644
--- a/lib/feature/gitaly.rb
+++ b/lib/feature/gitaly.rb
@@ -5,16 +5,12 @@ require 'set'
class Feature
class Gitaly
# Server feature flags should use '_' to separate words.
- # CATFILE_CACHE sets an incorrect example
- CATFILE_CACHE = 'catfile-cache'.freeze
-
SERVER_FEATURE_FLAGS =
[
- CATFILE_CACHE,
'get_commit_signatures'.freeze
].freeze
- DEFAULT_ON_FLAGS = Set.new([CATFILE_CACHE]).freeze
+ DEFAULT_ON_FLAGS = Set.new([]).freeze
class << self
def enabled?(feature_flag)
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/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/asciidoc.rb b/lib/gitlab/asciidoc.rb
index 00c87cce7b6..da65caa6c9c 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -13,6 +13,7 @@ module Gitlab
MAX_INCLUDE_DEPTH = 5
DEFAULT_ADOC_ATTRS = {
'showtitle' => true,
+ 'sectanchors' => true,
'idprefix' => 'user-content-',
'idseparator' => '-',
'env' => 'gitlab',
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 4317992d933..82e0c7ceeaa 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -94,6 +94,7 @@ module Gitlab
end
end
+ # rubocop:disable Gitlab/RailsLogger
def rate_limit!(ip, success:, login:)
rate_limiter = Gitlab::Auth::IpRateLimiter.new(ip)
return unless rate_limiter.enabled?
@@ -114,6 +115,7 @@ module Gitlab
end
end
end
+ # rubocop:enable Gitlab/RailsLogger
private
diff --git a/lib/gitlab/auth/ldap/adapter.rb b/lib/gitlab/auth/ldap/adapter.rb
index 15b9d5ad6e9..bcb0ecccdf9 100644
--- a/lib/gitlab/auth/ldap/adapter.rb
+++ b/lib/gitlab/auth/ldap/adapter.rb
@@ -55,7 +55,7 @@ module Gitlab
response = ldap.get_operation_result
unless response.code.zero?
- Rails.logger.warn("LDAP search error: #{response.message}")
+ Rails.logger.warn("LDAP search error: #{response.message}") # rubocop:disable Gitlab/RailsLogger
end
[]
@@ -67,7 +67,7 @@ module Gitlab
retries += 1
error_message = connection_error_message(error)
- Rails.logger.warn(error_message)
+ Rails.logger.warn(error_message) # rubocop:disable Gitlab/RailsLogger
if retries < MAX_SEARCH_RETRIES
renew_connection_adapter
diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb
index 47d63eb53cf..354f91306f9 100644
--- a/lib/gitlab/auth/ldap/config.rb
+++ b/lib/gitlab/auth/ldap/config.rb
@@ -240,7 +240,7 @@ module Gitlab
begin
custom_options[:cert] = OpenSSL::X509::Certificate.new(custom_options[:cert])
rescue OpenSSL::X509::CertificateError => e
- Rails.logger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}"
+ Rails.logger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
@@ -248,7 +248,7 @@ module Gitlab
begin
custom_options[:key] = OpenSSL::PKey.read(custom_options[:key])
rescue OpenSSL::PKey::PKeyError => e
- Rails.logger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}"
+ Rails.logger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index c1517222956..11a4052a109 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -45,7 +45,7 @@ module Gitlab
def self.normalize_dn(dn)
::Gitlab::Auth::LDAP::DN.new(dn).to_normalized_s
rescue ::Gitlab::Auth::LDAP::DN::FormatError => e
- Rails.logger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}")
+ Rails.logger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}") # rubocop:disable Gitlab/RailsLogger
dn
end
@@ -57,13 +57,13 @@ module Gitlab
def self.normalize_uid(uid)
::Gitlab::Auth::LDAP::DN.normalize_value(uid)
rescue ::Gitlab::Auth::LDAP::DN::FormatError => e
- Rails.logger.info("Returning original UID \"#{uid}\" due to error during normalization attempt: #{e.message}")
+ Rails.logger.info("Returning original UID \"#{uid}\" due to error during normalization attempt: #{e.message}") # rubocop:disable Gitlab/RailsLogger
uid
end
def initialize(entry, provider)
- Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
+ Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } # rubocop:disable Gitlab/RailsLogger
@entry = entry
@provider = provider
end
diff --git a/lib/gitlab/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/add_merge_request_diff_commits_count.rb b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
index cb2bdea755c..c912628d0fc 100644
--- a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
+++ b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
@@ -9,7 +9,7 @@ module Gitlab
end
def perform(start_id, stop_id)
- Rails.logger.info("Setting commits_count for merge request diffs: #{start_id} - #{stop_id}")
+ Rails.logger.info("Setting commits_count for merge request diffs: #{start_id} - #{stop_id}") # rubocop:disable Gitlab/RailsLogger
update = '
commits_count = (
diff --git a/lib/gitlab/background_migration/archive_legacy_traces.rb b/lib/gitlab/background_migration/archive_legacy_traces.rb
index 7ee783b8489..3c26982729d 100644
--- a/lib/gitlab/background_migration/archive_legacy_traces.rb
+++ b/lib/gitlab/background_migration/archive_legacy_traces.rb
@@ -14,7 +14,7 @@ module Gitlab
build.trace.archive!
rescue => e
- Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}"
+ Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/background_migration/calculate_wiki_sizes.rb b/lib/gitlab/background_migration/calculate_wiki_sizes.rb
index 886c41a2b9d..e62f5edd0e7 100644
--- a/lib/gitlab/background_migration/calculate_wiki_sizes.rb
+++ b/lib/gitlab/background_migration/calculate_wiki_sizes.rb
@@ -10,7 +10,7 @@ module Gitlab
.includes(project: [:route, :group, namespace: [:owner]]).find_each do |statistics|
statistics.refresh!(only: [:wiki_size])
rescue => e
- Rails.logger.error "Failed to update wiki statistics. id: #{statistics.id} message: #{e.message}"
+ Rails.logger.error "Failed to update wiki statistics. id: #{statistics.id} message: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
index 0e93b2cb2fa..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,20 +19,13 @@ 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}"
+ Rails.logger.error "Failed to update pages domain certificate valid time. id: #{domain.id}, message: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/background_migration/fix_cross_project_label_links.rb b/lib/gitlab/background_migration/fix_cross_project_label_links.rb
index bf5d7f5f322..20a98c8e141 100644
--- a/lib/gitlab/background_migration/fix_cross_project_label_links.rb
+++ b/lib/gitlab/background_migration/fix_cross_project_label_links.rb
@@ -108,7 +108,7 @@ module Gitlab
next unless matching_label
- Rails.logger.info "#{resource.class.name.demodulize} #{resource.id}: replacing #{label.label_id} with #{matching_label.id}"
+ Rails.logger.info "#{resource.class.name.demodulize} #{resource.id}: replacing #{label.label_id} with #{matching_label.id}" # rubocop:disable Gitlab/RailsLogger
LabelLink.update(label.label_link_id, label_id: matching_label.id)
end
end
diff --git a/lib/gitlab/background_migration/fix_pages_access_level.rb b/lib/gitlab/background_migration/fix_pages_access_level.rb
new file mode 100644
index 00000000000..0d49f3dd8c5
--- /dev/null
+++ b/lib/gitlab/background_migration/fix_pages_access_level.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # corrects stored pages access level on db depending on project visibility
+ class FixPagesAccessLevel
+ # Copy routable here to avoid relying on application logic
+ module Routable
+ def build_full_path
+ if parent && path
+ parent.build_full_path + '/' + path
+ else
+ path
+ end
+ end
+ end
+
+ # Namespace
+ class Namespace < ApplicationRecord
+ self.table_name = 'namespaces'
+ self.inheritance_column = :_type_disabled
+
+ include Routable
+
+ belongs_to :parent, class_name: "Namespace"
+ end
+
+ # Project
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+ self.inheritance_column = :_type_disabled
+
+ include Routable
+
+ belongs_to :namespace
+ alias_method :parent, :namespace
+ alias_attribute :parent_id, :namespace_id
+
+ PRIVATE = 0
+ INTERNAL = 10
+ PUBLIC = 20
+
+ def pages_deployed?
+ Dir.exist?(public_pages_path)
+ end
+
+ def public_pages_path
+ File.join(pages_path, 'public')
+ end
+
+ def pages_path
+ # TODO: when we migrate Pages to work with new storage types, change here to use disk_path
+ File.join(Settings.pages.path, build_full_path)
+ end
+ end
+
+ # ProjectFeature
+ class ProjectFeature < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'project_features'
+
+ belongs_to :project
+
+ PRIVATE = 10
+ ENABLED = 20
+ PUBLIC = 30
+ end
+
+ def perform(start_id, stop_id)
+ fix_public_access_level(start_id, stop_id)
+
+ make_internal_projects_public(start_id, stop_id)
+
+ fix_private_access_level(start_id, stop_id)
+ end
+
+ private
+
+ def access_control_is_enabled
+ @access_control_is_enabled = Gitlab.config.pages.access_control
+ end
+
+ # Public projects are allowed to have only enabled pages_access_level
+ # which is equivalent to public
+ def fix_public_access_level(start_id, stop_id)
+ project_features(start_id, stop_id, ProjectFeature::PUBLIC, Project::PUBLIC).each_batch do |features|
+ features.update_all(pages_access_level: ProjectFeature::ENABLED)
+ end
+ end
+
+ # If access control is disabled and project has pages deployed
+ # project will become unavailable when access control will become enabled
+ # we make these projects public to avoid negative surprise to user
+ def make_internal_projects_public(start_id, stop_id)
+ return if access_control_is_enabled
+
+ project_features(start_id, stop_id, ProjectFeature::ENABLED, Project::INTERNAL).find_each do |project_feature|
+ next unless project_feature.project.pages_deployed?
+
+ project_feature.update(pages_access_level: ProjectFeature::PUBLIC)
+ end
+ end
+
+ # Private projects are not allowed to have enabled access level, only `private` and `public`
+ # If access control is enabled, these projects currently behave as if the have `private` pages_access_level
+ # if access control is disabled, these projects currently behave as if the have `public` pages_access_level
+ # so we preserve this behaviour for projects with pages already deployed
+ # for project without pages we always set `private` access_level
+ def fix_private_access_level(start_id, stop_id)
+ project_features(start_id, stop_id, ProjectFeature::ENABLED, Project::PRIVATE).find_each do |project_feature|
+ if access_control_is_enabled
+ project_feature.update!(pages_access_level: ProjectFeature::PRIVATE)
+ else
+ fixed_access_level = project_feature.project.pages_deployed? ? ProjectFeature::PUBLIC : ProjectFeature::PRIVATE
+ project_feature.update!(pages_access_level: fixed_access_level)
+ end
+ end
+ end
+
+ def project_features(start_id, stop_id, pages_access_level, project_visibility_level)
+ ProjectFeature.where(id: start_id..stop_id).joins(:project)
+ .where(pages_access_level: pages_access_level)
+ .where(projects: { visibility_level: project_visibility_level })
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/fix_user_namespace_names.rb b/lib/gitlab/background_migration/fix_user_namespace_names.rb
new file mode 100644
index 00000000000..1a207121be0
--- /dev/null
+++ b/lib/gitlab/background_migration/fix_user_namespace_names.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This migration fixes the namespaces.name for all user-namespaces that have names
+ # that aren't equal to the users name.
+ # Then it uses the updated names of the namespaces to update the associated routes
+ # For more info see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23272
+ class FixUserNamespaceNames
+ def perform(from_id, to_id)
+ fix_namespace_names(from_id, to_id)
+ fix_namespace_route_names(from_id, to_id)
+ end
+
+ def fix_namespace_names(from_id, to_id)
+ ActiveRecord::Base.connection.execute <<~UPDATE_NAMESPACES
+ WITH namespaces_to_update AS (
+ SELECT
+ namespaces.id,
+ users.name AS correct_name
+ FROM
+ namespaces
+ INNER JOIN users ON namespaces.owner_id = users.id
+ WHERE
+ namespaces.type IS NULL
+ AND namespaces.id BETWEEN #{from_id} AND #{to_id}
+ AND namespaces.name != users.name
+ )
+ UPDATE
+ namespaces
+ SET
+ name = correct_name
+ FROM
+ namespaces_to_update
+ WHERE
+ namespaces.id = namespaces_to_update.id
+ UPDATE_NAMESPACES
+ end
+
+ def fix_namespace_route_names(from_id, to_id)
+ ActiveRecord::Base.connection.execute <<~ROUTES_UPDATE
+ WITH routes_to_update AS (
+ SELECT
+ routes.id,
+ users.name AS correct_name
+ FROM
+ routes
+ INNER JOIN namespaces ON routes.source_id = namespaces.id
+ INNER JOIN users ON namespaces.owner_id = users.id
+ WHERE
+ namespaces.type IS NULL
+ AND routes.source_type = 'Namespace'
+ AND namespaces.id BETWEEN #{from_id} AND #{to_id}
+ AND (routes.name != users.name OR routes.name IS NULL)
+ )
+ UPDATE
+ routes
+ SET
+ name = correct_name
+ FROM
+ routes_to_update
+ WHERE
+ routes_to_update.id = routes.id
+ ROUTES_UPDATE
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/fix_user_project_route_names.rb b/lib/gitlab/background_migration/fix_user_project_route_names.rb
new file mode 100644
index 00000000000..b84ff32e712
--- /dev/null
+++ b/lib/gitlab/background_migration/fix_user_project_route_names.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This migration fixes the routes.name for all user-projects that have names
+ # that don't start with the users name.
+ # For more info see https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23272
+ class FixUserProjectRouteNames
+ def perform(from_id, to_id)
+ ActiveRecord::Base.connection.execute <<~ROUTES_UPDATE
+ WITH routes_to_update AS (
+ SELECT
+ routes.id,
+ users.name || ' / ' || projects.name AS correct_name
+ FROM
+ routes
+ INNER JOIN projects ON routes.source_id = projects.id
+ INNER JOIN namespaces ON projects.namespace_id = namespaces.id
+ INNER JOIN users ON namespaces.owner_id = users.id
+ WHERE
+ routes.source_type = 'Project'
+ AND routes.id BETWEEN #{from_id} AND #{to_id}
+ AND namespaces.type IS NULL
+ AND (routes.name NOT LIKE users.name || '%' OR routes.name IS NULL)
+ )
+ UPDATE
+ routes
+ SET
+ name = routes_to_update.correct_name
+ FROM
+ routes_to_update
+ WHERE
+ routes_to_update.id = routes.id
+ ROUTES_UPDATE
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb b/lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb
new file mode 100644
index 00000000000..32ed6a2756d
--- /dev/null
+++ b/lib/gitlab/background_migration/migrate_null_private_profile_to_false.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class is responsible for migrating a range of users with private_profile == NULL to false
+ class MigrateNullPrivateProfileToFalse
+ # Temporary AR class for users
+ class User < ActiveRecord::Base
+ self.table_name = 'users'
+ end
+
+ def perform(start_id, stop_id)
+ User.where(private_profile: nil, id: start_id..stop_id).update_all(private_profile: false)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
index a4c6540c61b..eb4bc0aaf28 100644
--- a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
+++ b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
@@ -18,6 +18,14 @@ module Gitlab
execute("INSERT INTO merge_request_assignees (merge_request_id, user_id) #{select_sql}")
end
+ def perform_all_sync(batch_size:)
+ MergeRequest.each_batch(of: batch_size) do |batch|
+ range = batch.pluck('MIN(id)', 'MAX(id)').first
+
+ perform(*range)
+ end
+ end
+
private
def merge_request_assignees_not_exists_clause
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb
index 755b5ee725a..d2924d10225 100644
--- a/lib/gitlab/background_migration/populate_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb
@@ -42,7 +42,7 @@ module Gitlab
#{e.message}
#{e.backtrace.join("\n ")}
MSG
- Rails.logger.error(msg)
+ Rails.logger.error(msg) # rubocop:disable Gitlab/RailsLogger
false
end
end
diff --git a/lib/gitlab/background_migration/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 1ee44a3a5a9..7d40b459c9a 100644
--- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -111,7 +111,7 @@ module Gitlab
cmd = %W[#{ionice} -c Idle] + cmd if ionice
log_msg = "PrepareUntrackedUploads find command: \"#{cmd.join(' ')}\""
- Rails.logger.info log_msg
+ Rails.logger.info log_msg # rubocop:disable Gitlab/RailsLogger
cmd
end
@@ -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
diff --git a/lib/gitlab/batch_pop_queueing.rb b/lib/gitlab/batch_pop_queueing.rb
new file mode 100644
index 00000000000..61011abddf5
--- /dev/null
+++ b/lib/gitlab/batch_pop_queueing.rb
@@ -0,0 +1,112 @@
+# frozen_string_literal: true
+
+module Gitlab
+ ##
+ # This class is a queuing system for processing expensive tasks in an atomic manner
+ # with batch poping to let you optimize the total processing time.
+ #
+ # In usual queuing system, the first item started being processed immediately
+ # and the following items wait until the next items have been popped from the queue.
+ # On the other hand, this queueing system, the former part is same, however,
+ # it pops the enqueued items as batch. This is especially useful when you want to
+ # drop redandant items from the queue in order to process important items only,
+ # thus it's more efficient than the traditional queueing system.
+ #
+ # Caveats:
+ # - The order of the items are not guaranteed because of `sadd` (Redis Sets).
+ #
+ # Example:
+ # ```
+ # class TheWorker
+ # def perform
+ # result = Gitlab::BatchPopQueueing.new('feature', 'queue').safe_execute([item]) do |items_in_queue|
+ # item = extract_the_most_important_item_from(items_in_queue)
+ # expensive_process(item)
+ # end
+ #
+ # if result[:status] == :finished && result[:new_items].present?
+ # item = extract_the_most_important_item_from(items_in_queue)
+ # TheWorker.perform_async(item.id)
+ # end
+ # end
+ # end
+ # ```
+ #
+ class BatchPopQueueing
+ attr_reader :namespace, :queue_id
+
+ EXTRA_QUEUE_EXPIRE_WINDOW = 1.hour
+ MAX_COUNTS_OF_POP_ALL = 1000
+
+ # Initialize queue
+ #
+ # @param [String] namespace The namespace of the exclusive lock and queue key. Typically, it's a feature name.
+ # @param [String] queue_id The identifier of the queue.
+ # @return [Boolean]
+ def initialize(namespace, queue_id)
+ raise ArgumentError if namespace.empty? || queue_id.empty?
+
+ @namespace, @queue_id = namespace, queue_id
+ end
+
+ ##
+ # Execute the given block in an exclusive lock.
+ # If there is the other thread has already working on the block,
+ # it enqueues the items without processing the block.
+ #
+ # @param [Array<String>] new_items New items to be added to the queue.
+ # @param [Time] lock_timeout The timeout of the exclusive lock. Generally, this value should be longer than the maximum prosess timing of the given block.
+ # @return [Hash]
+ # - status => One of the `:enqueued` or `:finished`.
+ # - new_items => Newly enqueued items during the given block had been processed.
+ #
+ # NOTE: If an exception is raised in the block, the poppped items will not be recovered.
+ # We should NOT re-enqueue the items in this case because it could end up in an infinite loop.
+ def safe_execute(new_items, lock_timeout: 10.minutes, &block)
+ enqueue(new_items, lock_timeout + EXTRA_QUEUE_EXPIRE_WINDOW)
+
+ lease = Gitlab::ExclusiveLease.new(lock_key, timeout: lock_timeout)
+
+ return { status: :enqueued } unless uuid = lease.try_obtain
+
+ begin
+ all_args = pop_all
+
+ yield all_args if block_given?
+
+ { status: :finished, new_items: peek_all }
+ ensure
+ Gitlab::ExclusiveLease.cancel(lock_key, uuid)
+ end
+ end
+
+ private
+
+ def lock_key
+ @lock_key ||= "batch_pop_queueing:lock:#{namespace}:#{queue_id}"
+ end
+
+ def queue_key
+ @queue_key ||= "batch_pop_queueing:queue:#{namespace}:#{queue_id}"
+ end
+
+ def enqueue(items, expire_time)
+ Gitlab::Redis::Queues.with do |redis|
+ redis.sadd(queue_key, items)
+ redis.expire(queue_key, expire_time.to_i)
+ end
+ end
+
+ def pop_all
+ Gitlab::Redis::Queues.with do |redis|
+ redis.spop(queue_key, MAX_COUNTS_OF_POP_ALL)
+ end
+ end
+
+ def peek_all
+ Gitlab::Redis::Queues.with do |redis|
+ redis.smembers(queue_key)
+ end
+ end
+ end
+end
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/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index c911bfa7ff6..afad391e8e0 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -20,6 +20,12 @@ module Gitlab
end
end
+ def uses_unsupported_legacy_trigger?
+ trigger_request.present? &&
+ trigger_request.trigger.legacy? &&
+ !trigger_request.trigger.supports_legacy_tokens?
+ end
+
def branch_exists?
strong_memoize(:is_branch) do
project.repository.branch_exists?(ref)
diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
index aaa3daddcc5..357a1d55b3b 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
@@ -14,6 +14,10 @@ module Gitlab
return error('Pipelines are disabled!')
end
+ if @command.uses_unsupported_legacy_trigger?
+ return error('Trigger token is invalid because is not owned by any user')
+ end
+
unless allowed_to_trigger_pipeline?
if can?(current_user, :create_pipeline, project)
return error("Insufficient permissions for protected ref '#{command.ref}'")
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
index e4cf360a1c1..0212fa9d661 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb
@@ -8,11 +8,10 @@ module Gitlab
require_dependency 're2'
class Pattern < Lexeme::Value
- PATTERN = %r{^/.+/[ismU]*$}.freeze
- NEW_PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
+ PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
def initialize(regexp)
- @value = self.class.eager_matching_with_escape_characters? ? regexp.gsub(/\\\//, '/') : regexp
+ @value = regexp.gsub(/\\\//, '/')
unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value)
raise Lexer::SyntaxError, 'Invalid regular expression!'
@@ -26,16 +25,12 @@ module Gitlab
end
def self.pattern
- eager_matching_with_escape_characters? ? NEW_PATTERN : PATTERN
+ PATTERN
end
def self.build(string)
new(string)
end
-
- def self.eager_matching_with_escape_characters?
- Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
- end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/lexer.rb b/lib/gitlab/ci/pipeline/expression/lexer.rb
index 22c210ae26b..7d7582612f9 100644
--- a/lib/gitlab/ci/pipeline/expression/lexer.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexer.rb
@@ -17,17 +17,6 @@ module Gitlab
Expression::Lexeme::Equals,
Expression::Lexeme::Matches,
Expression::Lexeme::NotEquals,
- Expression::Lexeme::NotMatches
- ].freeze
-
- NEW_LEXEMES = [
- Expression::Lexeme::Variable,
- Expression::Lexeme::String,
- Expression::Lexeme::Pattern,
- Expression::Lexeme::Null,
- Expression::Lexeme::Equals,
- Expression::Lexeme::Matches,
- Expression::Lexeme::NotEquals,
Expression::Lexeme::NotMatches,
Expression::Lexeme::And,
Expression::Lexeme::Or
@@ -58,7 +47,7 @@ module Gitlab
return tokens if @scanner.eos?
- lexeme = available_lexemes.find do |type|
+ lexeme = LEXEMES.find do |type|
type.scan(@scanner).tap do |token|
tokens.push(token) if token.present?
end
@@ -71,10 +60,6 @@ module Gitlab
raise Lexer::SyntaxError, 'Too many tokens!'
end
-
- def available_lexemes
- Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true) ? NEW_LEXEMES : LEXEMES
- end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/expression/parser.rb b/lib/gitlab/ci/pipeline/expression/parser.rb
index 589bf32a4d7..edb55edf356 100644
--- a/lib/gitlab/ci/pipeline/expression/parser.rb
+++ b/lib/gitlab/ci/pipeline/expression/parser.rb
@@ -13,39 +13,6 @@ module Gitlab
end
def tree
- if Feature.enabled?(:ci_variables_complex_expressions, default_enabled: true)
- rpn_parse_tree
- else
- reverse_descent_parse_tree
- end
- end
-
- def self.seed(statement)
- new(Expression::Lexer.new(statement).tokens)
- end
-
- private
-
- # This produces a reverse descent parse tree.
- # It does not support precedence of operators.
- def reverse_descent_parse_tree
- while token = @tokens.next
- case token.type
- when :operator
- token.build(@nodes.pop, tree).tap do |node|
- @nodes.push(node)
- end
- when :value
- token.build.tap do |leaf|
- @nodes.push(leaf)
- end
- end
- end
- rescue StopIteration
- @nodes.last || Lexeme::Null.new
- end
-
- def rpn_parse_tree
results = []
tokens_rpn.each do |token|
@@ -70,6 +37,12 @@ module Gitlab
results.pop
end
+ def self.seed(statement)
+ new(Expression::Lexer.new(statement).tokens)
+ end
+
+ private
+
# Parse the expression into Reverse Polish Notation
# (See: Shunting-yard algorithm)
def tokens_rpn
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index cf3d261c1cb..7b9a169a91f 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -57,6 +57,8 @@ variables:
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 8a84744aa2d..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
@@ -22,6 +23,7 @@ code_quality:
reports:
codequality: gl-code-quality-report.json
expire_in: 1 week
+ dependencies: []
only:
- branches
- tags
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index dcf8254ef94..6ead127e7b6 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -243,10 +243,10 @@ rollout 100%:
.deploy_helpers: &deploy_helpers |
[[ "$TRACE" ]] && set -x
- auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB}
+ export RELEASE_NAME=${HELM_RELEASE_NAME:-$CI_ENVIRONMENT_SLUG}
+ auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${RELEASE_NAME}-postgres:5432/${POSTGRES_DB}
export DATABASE_URL=${DATABASE_URL-$auto_database_url}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
function get_replicas() {
track="${1:-stable}"
@@ -308,7 +308,7 @@ rollout 100%:
}
function deploy_name() {
- name="$CI_ENVIRONMENT_SLUG"
+ name="$RELEASE_NAME"
track="${1-stable}"
if [[ "$track" != "stable" ]]; then
@@ -373,7 +373,7 @@ rollout 100%:
--set service.enabled="$service_enabled" \
--set gitlab.app="$CI_PROJECT_PATH_SLUG" \
--set gitlab.env="$CI_ENVIRONMENT_SLUG" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
+ --set releaseOverride="$RELEASE_NAME" \
--set image.repository="$image_repository" \
--set image.tag="$image_tag" \
--set image.pullPolicy=IfNotPresent \
@@ -414,7 +414,7 @@ rollout 100%:
--set service.enabled="$service_enabled" \
--set gitlab.app="$CI_PROJECT_PATH_SLUG" \
--set gitlab.env="$CI_ENVIRONMENT_SLUG" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
+ --set releaseOverride="$RELEASE_NAME" \
--set image.repository="$image_repository" \
--set image.tag="$image_tag" \
--set image.pullPolicy=IfNotPresent \
diff --git a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
index b9fee2d5731..25ea20e454f 100644
--- a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
@@ -1,5 +1,5 @@
# Select image from https://hub.docker.com/_/php/
-image: php:7.1.1
+image: php:latest
# Select what we should cache between builds
cache:
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
index d1a34c515fa..5ad624bb15f 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -23,6 +23,7 @@ container_scanning:
DOCKER_HOST: tcp://${DOCKER_SERVICE}:2375/
# https://hub.docker.com/r/arminc/clair-local-scan/tags
CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
+ CLAIR_EXECUTABLE_VERSION: v11
## Disable the proxy for clair-local-scan, otherwise Container Scanning will
## fail when a proxy is used.
NO_PROXY: ${DOCKER_SERVICE},localhost
@@ -41,7 +42,7 @@ container_scanning:
- docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION}
- apk add -U wget ca-certificates
- docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+ - wget https://github.com/arminc/clair-scanner/releases/download/${CLAIR_EXECUTABLE_VERSION}/clair-scanner_linux_amd64
- mv clair-scanner_linux_amd64 clair-scanner
- chmod +x clair-scanner
- touch clair-whitelist.yml
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index 8dd9775c583..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
@@ -40,6 +41,10 @@ dependency_scanning:
DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
DS_PULL_ANALYZER_IMAGE_TIMEOUT \
DS_RUN_ANALYZER_TIMEOUT \
+ DS_PYTHON_VERSION \
+ DS_PIP_DEPENDENCY_PATH \
+ PIP_INDEX_URL \
+ PIP_EXTRA_INDEX_URL \
) \
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 0a97a16b83c..4190de73e1f 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
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..cb617080c76 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
@@ -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/cleanup/orphan_job_artifact_files.rb b/lib/gitlab/cleanup/orphan_job_artifact_files.rb
index ee7164b3e55..808814c39e0 100644
--- a/lib/gitlab/cleanup/orphan_job_artifact_files.rb
+++ b/lib/gitlab/cleanup/orphan_job_artifact_files.rb
@@ -17,7 +17,7 @@ module Gitlab
@limit = limit
@dry_run = dry_run
@niceness = niceness || DEFAULT_NICENESS
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
@total_found = @total_cleaned = 0
new_batch!
diff --git a/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb b/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb
index 5c30258c0fc..53e0c83046e 100644
--- a/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb
+++ b/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb
@@ -22,7 +22,7 @@ module Gitlab
attr_reader :batch_size, :dry_run
attr_accessor :artifact_files
- def initialize(batch_size:, dry_run: true, logger: Rails.logger)
+ def initialize(batch_size:, dry_run: true, logger: Rails.logger) # rubocop:disable Gitlab/RailsLogger
@batch_size = batch_size
@dry_run = dry_run
@logger = logger
diff --git a/lib/gitlab/cleanup/project_upload_file_finder.rb b/lib/gitlab/cleanup/project_upload_file_finder.rb
index 2ee8b60e76a..5aace564c2d 100644
--- a/lib/gitlab/cleanup/project_upload_file_finder.rb
+++ b/lib/gitlab/cleanup/project_upload_file_finder.rb
@@ -49,7 +49,7 @@ module Gitlab
cmd = %W[#{ionice} -c Idle] + cmd if ionice
log_msg = "find command: \"#{cmd.join(' ')}\""
- Rails.logger.info log_msg
+ Rails.logger.info log_msg # rubocop:disable Gitlab/RailsLogger
cmd
end
diff --git a/lib/gitlab/cleanup/project_uploads.rb b/lib/gitlab/cleanup/project_uploads.rb
index 82a405362c2..056e075cb21 100644
--- a/lib/gitlab/cleanup/project_uploads.rb
+++ b/lib/gitlab/cleanup/project_uploads.rb
@@ -8,7 +8,7 @@ module Gitlab
attr_reader :logger
def initialize(logger: nil)
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
end
def run!(dry_run: true)
diff --git a/lib/gitlab/cleanup/remote_uploads.rb b/lib/gitlab/cleanup/remote_uploads.rb
index 03298d960a4..42c93b7aecb 100644
--- a/lib/gitlab/cleanup/remote_uploads.rb
+++ b/lib/gitlab/cleanup/remote_uploads.rb
@@ -7,7 +7,7 @@ module Gitlab
BATCH_SIZE = 100
def initialize(logger: nil)
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
end
def run!(dry_run: false)
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 469a7fd9f7b..32d5e4b9ea3 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -45,7 +45,7 @@ module Gitlab
# need to be added to the application settings. To prevent Rake tasks
# and other callers from failing, use any loaded settings and return
# defaults for missing columns.
- if ActiveRecord::Migrator.needs_migration?
+ if ActiveRecord::Base.connection.migration_context.needs_migration?
db_attributes = current_settings&.attributes || {}
fake_application_settings(db_attributes)
elsif current_settings.present?
diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
index 304d60996a6..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
+ attr_reader :projections, :query, :stage, :order, :options
MAX_EVENTS = 50
- def initialize(project:, stage:, options:)
- @project = project
+ def initialize(stage:, options:)
@stage = stage
@options = options
end
@@ -40,13 +40,13 @@ module Gitlab
end
def events_query
- diff_fn = subtract_datetimes_diff(base_query, @options[:start_time_attrs], @options[:end_time_attrs])
+ diff_fn = subtract_datetimes_diff(base_query, options[:start_time_attrs], options[:end_time_attrs])
base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *projections).order(order.desc).take(MAX_EVENTS)
end
def default_order
- [@options[:start_time_attrs]].flatten.first
+ [options[:start_time_attrs]].flatten.first
end
def serialize(_event)
@@ -59,13 +59,21 @@ module Gitlab
def allowed_ids
@allowed_ids ||= allowed_ids_finder_class
- .new(@options[:current_user], project_id: @project.id)
+ .new(options[:current_user], allowed_ids_source)
.execute.where(id: event_result_ids).pluck(:id)
end
def event_result_ids
event_result.map { |event| event['id'] }
end
+
+ def allowed_ids_source
+ group ? { group_id: group.id, include_subgroups: true } : { project_id: project.id }
+ end
+
+ def serialization_context
+ {}
+ end
end
end
end
diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb
index 36231b187cd..9c98c0bfbf2 100644
--- a/lib/gitlab/cycle_analytics/base_query.rb
+++ b/lib/gitlab/cycle_analytics/base_query.rb
@@ -10,23 +10,31 @@ module Gitlab
private
def base_query
- @base_query ||= stage_query(@project.id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @base_query ||= stage_query(projects.map(&:id))
end
def stage_query(project_ids)
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])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ .where(routes_table[:source_type].eq('Namespace'))
+ .where(issue_table[:created_at].gteq(options[:from]))
# Load merge_requests
- query = query.join(mr_table, Arel::Nodes::OuterJoin)
+
+ query = load_merge_requests(query)
+
+ query
+ 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 e2d6a301734..1cd54238bb4 100644
--- a/lib/gitlab/cycle_analytics/base_stage.rb
+++ b/lib/gitlab/cycle_analytics/base_stage.rb
@@ -4,9 +4,11 @@ module Gitlab
module CycleAnalytics
class BaseStage
include BaseQuery
+ include GroupProjectsProvider
- def initialize(project:, options:)
- @project = project
+ attr_reader :options
+
+ def initialize(options:)
@options = options
end
@@ -14,30 +16,23 @@ module Gitlab
event_fetcher.fetch
end
- def as_json
- AnalyticsStageSerializer.new.represent(self)
+ def as_json(serializer: AnalyticsStageSerializer)
+ serializer.new.represent(self)
end
def title
raise NotImplementedError.new("Expected #{self.name} to implement title")
end
- def median
- BatchLoader.for(@project.id).batch(key: name) do |project_ids, loader|
- cte_table = Arel::Table.new("cte_table_for_#{name}")
-
- # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
- # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
- # We compute the (end_time - start_time) interval, and give it an alias based on the current
- # cycle analytics stage.
- interval_query = Arel::Nodes::As.new(cte_table,
- subtract_datetimes(stage_query(project_ids), start_time_attrs, end_time_attrs, name.to_s))
+ def project_median
+ return if project.nil?
+ BatchLoader.for(project.id).batch(key: name) do |project_ids, loader|
if project_ids.one?
- loader.call(@project.id, median_datetime(cte_table, interval_query, name))
+ loader.call(project.id, median_query(project_ids))
else
begin
- median_datetimes(cte_table, interval_query, name, :project_id)&.each do |project_id, median|
+ median_datetimes(cte_table, interval_query(project_ids), name, :project_id)&.each do |project_id, median|
loader.call(project_id, median)
end
rescue NotSupportedError
@@ -47,20 +42,41 @@ 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).
+ # We compute the (end_time - start_time) interval, and give it an alias based on the current
+ # cycle analytics stage.
+
+ median_datetime(cte_table, interval_query(project_ids), name)
+ end
+
def name
raise NotImplementedError.new("Expected #{self.name} to implement name")
end
+ def cte_table
+ Arel::Table.new("cte_table_for_#{name}")
+ end
+
+ def interval_query(project_ids)
+ Arel::Nodes::As.new(cte_table,
+ subtract_datetimes(stage_query(project_ids), start_time_attrs, end_time_attrs, name.to_s))
+ end
+
private
def event_fetcher
- @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(project: @project,
- 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)
+ options.merge(start_time_attrs: start_time_attrs, end_time_attrs: end_time_attrs)
end
end
end
diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
index 6c348f1862d..1e4e9b9e02c 100644
--- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
@@ -11,7 +11,9 @@ module Gitlab
mr_table[:id],
mr_table[:created_at],
mr_table[:state],
- mr_table[:author_id]]
+ mr_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
@order = mr_table[:created_at]
super(*args)
@@ -20,7 +22,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 8a870f2e2a3..2d03e425a6a 100644
--- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
- issue_table[:author_id]]
+ issue_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
super(*args)
end
@@ -18,7 +20,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 c9266341378..0fc4f1dd41a 100644
--- a/lib/gitlab/cycle_analytics/issue_helper.rb
+++ b/lib/gitlab/cycle_analytics/issue_helper.rb
@@ -5,9 +5,12 @@ 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])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ .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)))
query
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 afefd09b614..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::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 d924f956dcd..77cc358daa9 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
- issue_table[:author_id]]
+ issue_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
super(*args)
end
@@ -18,7 +20,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 30fc2ce6d40..c3f742503a9 100644
--- a/lib/gitlab/cycle_analytics/plan_helper.rb
+++ b/lib/gitlab/cycle_analytics/plan_helper.rb
@@ -5,14 +5,21 @@ 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"))
.where(issue_table[:project_id].in(project_ids))
- .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
- .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 = add_conditions_to_query(query)
query
end
+
+ def add_conditions_to_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 6bcbe0412a9..404b2460814 100644
--- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/production_event_fetcher.rb
@@ -10,7 +10,9 @@ module Gitlab
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
- issue_table[:author_id]]
+ issue_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
super(*args)
end
@@ -18,7 +20,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/production_helper.rb b/lib/gitlab/cycle_analytics/production_helper.rb
index aff65b150fb..778757a9ede 100644
--- a/lib/gitlab/cycle_analytics/production_helper.rb
+++ b/lib/gitlab/cycle_analytics/production_helper.rb
@@ -6,7 +6,7 @@ module Gitlab
def stage_query(project_ids)
super(project_ids)
.where(mr_metrics_table[:first_deployed_to_production_at]
- .gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ .gteq(options[:from]))
end
end
end
diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
index b6354b5ffad..6acd12517fa 100644
--- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
@@ -11,7 +11,9 @@ module Gitlab
mr_table[:id],
mr_table[:created_at],
mr_table[:state],
- mr_table[:author_id]]
+ mr_table[:author_id],
+ projects_table[:name],
+ routes_table[:path]]
super(*args)
end
@@ -19,7 +21,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/cycle_analytics/test_helper.rb b/lib/gitlab/cycle_analytics/test_helper.rb
index 32fca7fa898..d9124d62c7c 100644
--- a/lib/gitlab/cycle_analytics/test_helper.rb
+++ b/lib/gitlab/cycle_analytics/test_helper.rb
@@ -14,7 +14,7 @@ module Gitlab
private
def branch
- @branch ||= @options[:branch] # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @branch ||= options[:branch]
end
end
end
diff --git a/lib/gitlab/cycle_analytics/usage_data.rb b/lib/gitlab/cycle_analytics/usage_data.rb
index 913ee373f54..644300caead 100644
--- a/lib/gitlab/cycle_analytics/usage_data.rb
+++ b/lib/gitlab/cycle_analytics/usage_data.rb
@@ -32,7 +32,7 @@ module Gitlab
def medians_per_stage
projects.each_with_object({}) do |project, hsh|
- ::CycleAnalytics.new(project, options).all_medians_per_stage.each do |stage_name, median|
+ ::CycleAnalytics::ProjectLevel.new(project, options: options).all_medians_by_stage.each do |stage_name, median|
hsh[stage_name] ||= []
hsh[stage_name] << median
end
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index 0fc145534bf..c0a12318990 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|
@@ -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/database.rb b/lib/gitlab/database.rb
index 34c1e6ad8ca..eef63536de4 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,11 +39,11 @@ 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
def self.postgresql?
@@ -60,15 +60,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?
@@ -118,51 +117,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
-
- 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
-
- 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 +153,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 +162,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 +228,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
@@ -284,17 +250,28 @@ module Gitlab
end
# inside_transaction? will return true if the caller is running within a transaction. Handles special cases
- # when running inside a test environment, in which the entire test is running with a DatabaseCleaner transaction
+ # when running inside a test environment, where tests may be wrapped in transactions
def self.inside_transaction?
- ActiveRecord::Base.connection.open_transactions > open_transactions_baseline
+ if Rails.env.test?
+ ActiveRecord::Base.connection.open_transactions > open_transactions_baseline
+ else
+ ActiveRecord::Base.connection.open_transactions > 0
+ end
end
- def self.open_transactions_baseline
- if ::Rails.env.test?
- return DatabaseCleaner.connections.count { |conn| conn.strategy.is_a?(DatabaseCleaner::ActiveRecord::Transaction) }
- end
+ # These methods that access @open_transactions_baseline are not thread-safe.
+ # These are fine though because we only call these in RSpec's main thread. If we decide to run
+ # specs multi-threaded, we would need to use something like ThreadGroup to keep track of this value
+ def self.set_open_transactions_baseline
+ @open_transactions_baseline = ActiveRecord::Base.connection.open_transactions
+ end
+
+ def self.reset_open_transactions_baseline
+ @open_transactions_baseline = 0
+ end
- 0
+ def self.open_transactions_baseline
+ @open_transactions_baseline ||= 0
end
private_class_method :open_transactions_baseline
@@ -310,7 +287,7 @@ module Gitlab
gitlab_database_transaction_seconds.observe(labels, duration_seconds)
rescue Prometheus::Client::LabelSetValidator::LabelSetError => err
# Ensure that errors in recording these metrics don't affect the operation of the application
- Rails.logger.error("Unable to observe database transaction duration: #{err}")
+ Rails.logger.error("Unable to observe database transaction duration: #{err}") # rubocop:disable Gitlab/RailsLogger
end
# MonkeyPatch for ActiveRecord::Base for adding observability
diff --git a/lib/gitlab/database/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 862ab96c887..26adf4e221b 100644
--- a/lib/gitlab/database/grant.rb
+++ b/lib/gitlab/database/grant.rb
@@ -24,7 +24,7 @@ module Gitlab
begin
from(nil)
- .pluck("has_table_privilege(#{quoted_table}, 'TRIGGER')")
+ .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
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index 1455e410d4b..391c1e85a7d 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
@@ -158,7 +119,7 @@ module Gitlab
Arel::Nodes::Window.new.order(arel_table[column_sym])
).as('row_id')
- count = arel_table.project("COUNT(1)").as('ct')
+ count = arel_table.where(arel_table[column_sym].gteq(zero_interval)).project("COUNT(1)").as('ct')
[column_row, row_id, count]
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index e2cbf91f281..4bd09163bf2 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,12 +82,10 @@ 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}"
+ Rails.logger.warn "Index not created because it already exists (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}" # rubocop:disable Gitlab/RailsLogger
return
end
@@ -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:
#
@@ -91,7 +113,7 @@ module Gitlab
end
unless index_exists?(table_name, column_name, options)
- Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}"
+ Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}" # rubocop:disable Gitlab/RailsLogger
return
end
@@ -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:
#
@@ -121,7 +141,7 @@ module Gitlab
end
unless index_exists_by_name?(table_name, index_name)
- Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, index_name: #{index_name}"
+ Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, index_name: #{index_name}" # rubocop:disable Gitlab/RailsLogger
return
end
@@ -141,14 +161,15 @@ 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.
# column - The name of the column to create the foreign key on.
# on_delete - The action to perform when associated data is removed,
# defaults to "CASCADE".
+ #
+ # rubocop:disable Gitlab/RailsLogger
def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, name: nil)
# Transactions would result in ALTER TABLE locks being held for the
# duration of the transaction, defeating the purpose of this method.
@@ -156,27 +177,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)
@@ -208,6 +209,7 @@ module Gitlab
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
end
end
+ # rubocop:enable Gitlab/RailsLogger
def foreign_key_exists?(source, target = nil, column: nil)
foreign_keys(source).any? do |key|
@@ -233,7 +235,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:
#
@@ -245,15 +247,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')
@@ -503,13 +496,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.
@@ -552,11 +544,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
@@ -769,38 +757,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)
@@ -850,8 +812,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
@@ -891,26 +851,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)
@@ -952,11 +902,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
@@ -1059,10 +1005,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
#
@@ -1073,10 +1015,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
@@ -1092,8 +1032,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/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
index 6bbad707f0f..3e8a9b89998 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
@@ -70,7 +70,7 @@ module Gitlab
unless gitlab_shell.mv_namespace(repository_storage, old_full_path, new_full_path)
message = "Exception moving on shard #{repository_storage} from #{old_full_path} to #{new_full_path}"
- Rails.logger.error message
+ Rails.logger.error message # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
index 580be9fe267..4dc7a62797a 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
@@ -56,7 +56,7 @@ module Gitlab
unless gitlab_shell.mv_repository(project.repository_storage,
old_path,
new_path)
- Rails.logger.error "Error moving #{old_path} to #{new_path}"
+ Rails.logger.error "Error moving #{old_path} to #{new_path}" # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/lib/gitlab/database_importers/common_metrics.rb b/lib/gitlab/database_importers/common_metrics.rb
new file mode 100644
index 00000000000..f964ae8a275
--- /dev/null
+++ b/lib/gitlab/database_importers/common_metrics.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module CommonMetrics
+ end
+ end
+end
diff --git a/lib/gitlab/database_importers/common_metrics/importer.rb b/lib/gitlab/database_importers/common_metrics/importer.rb
new file mode 100644
index 00000000000..6c61e05674e
--- /dev/null
+++ b/lib/gitlab/database_importers/common_metrics/importer.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module CommonMetrics
+ class Importer
+ MissingQueryId = Class.new(StandardError)
+
+ attr_reader :content
+
+ def initialize(filename = 'common_metrics.yml')
+ @content = YAML.load_file(Rails.root.join('config', 'prometheus', filename))
+ end
+
+ def execute
+ CommonMetrics::PrometheusMetric.reset_column_information
+
+ process_content do |id, attributes|
+ find_or_build_metric!(id)
+ .update!(**attributes)
+ end
+ end
+
+ private
+
+ def process_content(&blk)
+ content['panel_groups'].map do |group|
+ process_group(group, &blk)
+ end
+ end
+
+ def process_group(group, &blk)
+ attributes = {
+ group: find_group_title_key(group['group'])
+ }
+
+ group['panels'].map do |panel|
+ process_panel(panel, attributes, &blk)
+ end
+ end
+
+ def process_panel(panel, attributes, &blk)
+ attributes = attributes.merge(
+ title: panel['title'],
+ y_label: panel['y_label'])
+
+ panel['metrics'].map do |metric_details|
+ process_metric_details(metric_details, attributes, &blk)
+ end
+ end
+
+ def process_metric_details(metric_details, attributes, &blk)
+ attributes = attributes.merge(
+ legend: metric_details['label'],
+ query: metric_details['query_range'],
+ unit: metric_details['unit'])
+
+ yield(metric_details['id'], attributes)
+ end
+
+ def find_or_build_metric!(id)
+ raise MissingQueryId unless id
+
+ CommonMetrics::PrometheusMetric.common.find_by(identifier: id) ||
+ CommonMetrics::PrometheusMetric.new(common: true, identifier: id)
+ end
+
+ def find_group_title_key(title)
+ CommonMetrics::PrometheusMetricEnums.groups[find_group_title(title)]
+ end
+
+ def find_group_title(title)
+ CommonMetrics::PrometheusMetricEnums.group_titles.invert[title]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database_importers/common_metrics/prometheus_metric.rb b/lib/gitlab/database_importers/common_metrics/prometheus_metric.rb
new file mode 100644
index 00000000000..b4a392cbea9
--- /dev/null
+++ b/lib/gitlab/database_importers/common_metrics/prometheus_metric.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module CommonMetrics
+ class PrometheusMetric < ApplicationRecord
+ enum group: PrometheusMetricEnums.groups
+ scope :common, -> { where(common: true) }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb b/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb
new file mode 100644
index 00000000000..c9e957ec7c0
--- /dev/null
+++ b/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module CommonMetrics
+ module PrometheusMetricEnums
+ def self.groups
+ {
+ # built-in groups
+ nginx_ingress_vts: -1,
+ ha_proxy: -2,
+ aws_elb: -3,
+ nginx: -4,
+ kubernetes: -5,
+ nginx_ingress: -6,
+
+ # custom groups
+ business: 0,
+ response: 1,
+ system: 2
+ }
+ end
+
+ def self.group_titles
+ {
+ business: _('Business metrics (Custom)'),
+ response: _('Response metrics (Custom)'),
+ system: _('System metrics (Custom)'),
+ nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'),
+ nginx_ingress: _('Response metrics (NGINX Ingress)'),
+ ha_proxy: _('Response metrics (HA Proxy)'),
+ aws_elb: _('Response metrics (AWS ELB)'),
+ nginx: _('Response metrics (NGINX)'),
+ kubernetes: _('System metrics (Kubernetes)')
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/hook/disable_email_interceptor.rb b/lib/gitlab/email/hook/disable_email_interceptor.rb
index 6b6b1d85109..58dc1527c7a 100644
--- a/lib/gitlab/email/hook/disable_email_interceptor.rb
+++ b/lib/gitlab/email/hook/disable_email_interceptor.rb
@@ -7,7 +7,7 @@ module Gitlab
def self.delivering_email(message)
message.perform_deliveries = false
- Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
+ Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index 5a61a7f5d60..88729babb2b 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -59,7 +59,7 @@ module Gitlab
begin
CharlockHolmes::Converter.convert(message, detect[:encoding], 'UTF-8')
rescue ArgumentError => e
- Rails.logger.warn("Ignoring error converting #{detect[:encoding]} into UTF8: #{e.message}")
+ Rails.logger.warn("Ignoring error converting #{detect[:encoding]} into UTF8: #{e.message}") # rubocop:disable Gitlab/RailsLogger
''
end
diff --git a/lib/gitlab/git.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 060a29be782..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
@@ -464,6 +468,18 @@ module Gitlab
end
end
+ # Returns path to url mappings for submodules
+ #
+ # Ex.
+ # @repository.submodule_urls_for('master')
+ # # => { 'rack' => 'git@localhost:rack.git' }
+ #
+ def submodule_urls_for(ref)
+ wrapped_gitaly_errors do
+ gitaly_submodule_urls_for(ref)
+ end
+ end
+
# Return total commits count accessible from passed ref
def commit_count(ref)
wrapped_gitaly_errors do
@@ -861,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
@@ -936,7 +952,7 @@ module Gitlab
gitaly_repository_client.cleanup if exists?
end
rescue Gitlab::Git::CommandError => e # Don't fail if we can't cleanup
- Rails.logger.error("Unable to clean repository on storage #{storage} with relative path #{relative_path}: #{e.message}")
+ Rails.logger.error("Unable to clean repository on storage #{storage} with relative path #{relative_path}: #{e.message}") # rubocop:disable Gitlab/RailsLogger
Gitlab::Metrics.counter(
:failed_repository_cleanup_total,
'Number of failed repository cleanup events'
@@ -1059,12 +1075,16 @@ module Gitlab
return unless commit_object && commit_object.type == :COMMIT
+ urls = gitaly_submodule_urls_for(ref)
+ urls && urls[path]
+ end
+
+ def gitaly_submodule_urls_for(ref)
gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
return unless gitmodules
- found_module = GitmodulesParser.new(gitmodules.data).parse[path]
-
- found_module && found_module['url']
+ submodules = GitmodulesParser.new(gitmodules.data).parse
+ submodules.transform_values { |submodule| submodule['url'] }
end
# Returns true if the given ref name exists
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..ded5349be01 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"
- end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index cf0157269a8..c98de722fe1 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -67,7 +67,7 @@ module Gitlab
File.read(cert_file).scan(PEM_REGEX).map do |cert|
OpenSSL::X509::Certificate.new(cert).to_pem
rescue OpenSSL::OpenSSLError => e
- Rails.logger.error "Could not load certificate #{cert_file} #{e}"
+ Rails.logger.error "Could not load certificate #{cert_file} #{e}" # rubocop:disable Gitlab/RailsLogger
Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file })
nil
end.compact
@@ -211,8 +211,7 @@ module Gitlab
metadata['call_site'] = feature.to_s if feature
metadata['gitaly-servers'] = address_metadata(remote_storage) if remote_storage
metadata['x-gitlab-correlation-id'] = Labkit::Correlation::CorrelationId.current_id if Labkit::Correlation::CorrelationId.current_id
- metadata['gitaly-session-id'] = session_id if Feature::Gitaly.enabled?(Feature::Gitaly::CATFILE_CACHE)
-
+ metadata['gitaly-session-id'] = session_id
metadata.merge!(Feature::Gitaly.server_feature_flags)
result = { metadata: metadata }
@@ -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::ACCESS, JSON::ParserError
nil
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/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index d8e9dccb644..ca3e5b51ecc 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -5,7 +5,7 @@ module Gitlab
class RepositoryService
include Gitlab::EncodingHelper
- MAX_MSG_SIZE = 128.kilobytes.freeze
+ MAX_MSG_SIZE = 128.kilobytes
def initialize(repository)
@repository = repository
diff --git a/lib/gitlab/github_import/importer/lfs_objects_importer.rb b/lib/gitlab/github_import/importer/lfs_objects_importer.rb
index 6046e30d4ef..30763492235 100644
--- a/lib/gitlab/github_import/importer/lfs_objects_importer.rb
+++ b/lib/gitlab/github_import/importer/lfs_objects_importer.rb
@@ -29,7 +29,7 @@ module Gitlab
yield object
end
rescue StandardError => e
- Rails.logger.error("The Lfs import process failed. #{e.message}")
+ Rails.logger.error("The Lfs import process failed. #{e.message}") # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/github_import/importer/pull_requests_importer.rb b/lib/gitlab/github_import/importer/pull_requests_importer.rb
index a52866c4b08..929fceaacf2 100644
--- a/lib/gitlab/github_import/importer/pull_requests_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_requests_importer.rb
@@ -40,7 +40,7 @@ module Gitlab
pname = project.path_with_namespace
- Rails.logger
+ Rails.logger # rubocop:disable Gitlab/RailsLogger
.info("GitHub importer finished updating repository for #{pname}")
repository_updates_counter.increment
diff --git a/lib/gitlab/global_id.rb b/lib/gitlab/global_id.rb
new file mode 100644
index 00000000000..cc82b6c5897
--- /dev/null
+++ b/lib/gitlab/global_id.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GlobalId
+ def self.build(object = nil, model_name: nil, id: nil, params: nil)
+ if object
+ model_name ||= object.class.name
+ id ||= object.id
+ end
+
+ ::URI::GID.build(app: GlobalID.app, model_name: model_name, model_id: id, params: params)
+ end
+ end
+end
diff --git a/lib/gitlab/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/graphql.rb b/lib/gitlab/graphql.rb
index 8a59e83974f..74c04e5380e 100644
--- a/lib/gitlab/graphql.rb
+++ b/lib/gitlab/graphql.rb
@@ -3,9 +3,5 @@
module Gitlab
module Graphql
StandardGraphqlError = Class.new(StandardError)
-
- def self.enabled?
- Feature.enabled?(:graphql, default_enabled: true)
- end
end
end
diff --git a/lib/gitlab/graphql/docs/helper.rb b/lib/gitlab/graphql/docs/helper.rb
new file mode 100644
index 00000000000..ac2a78c0f28
--- /dev/null
+++ b/lib/gitlab/graphql/docs/helper.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+return if Rails.env.production?
+
+module Gitlab
+ module Graphql
+ module Docs
+ # Helper with functions to be used by HAML templates
+ # This includes graphql-docs gem helpers class.
+ # You can check the included module on: https://github.com/gjtorikian/graphql-docs/blob/v1.6.0/lib/graphql-docs/helpers.rb
+ module Helper
+ include GraphQLDocs::Helpers
+
+ def auto_generated_comment
+ <<-MD.strip_heredoc
+ <!---
+ This documentation is auto generated by a script.
+
+ Please do not edit this file directly, check compile_docs task on lib/tasks/gitlab/graphql.rake.
+ --->
+ MD
+ end
+
+ # Some fields types are arrays of other types and are displayed
+ # on docs wrapped in square brackets, for example: [String!].
+ # This makes GitLab docs renderer thinks they are links so here
+ # we change them to be rendered as: String! => Array.
+ def render_field_type(type)
+ array_type = type[/\[(.+)\]/, 1]
+
+ if array_type
+ "#{array_type} => Array"
+ else
+ type
+ end
+ end
+
+ # We are ignoring connections and built in types for now,
+ # they should be added when queries are generated.
+ def objects
+ graphql_object_types.select do |object_type|
+ !object_type[:name]["Connection"] &&
+ !object_type[:name]["Edge"] &&
+ !object_type[:name]["__"]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/docs/renderer.rb b/lib/gitlab/graphql/docs/renderer.rb
new file mode 100644
index 00000000000..f47a372aa19
--- /dev/null
+++ b/lib/gitlab/graphql/docs/renderer.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+return if Rails.env.production?
+
+module Gitlab
+ module Graphql
+ module Docs
+ # Gitlab renderer for graphql-docs.
+ # Uses HAML templates to parse markdown and generate .md files.
+ # It uses graphql-docs helpers and schema parser, more information in https://github.com/gjtorikian/graphql-docs.
+ #
+ # Arguments:
+ # schema - the GraphQL schema defition. For GitLab should be: GitlabSchema.graphql_definition
+ # output_dir: The folder where the markdown files will be saved
+ # template: The path of the haml template to be parsed
+ class Renderer
+ include Gitlab::Graphql::Docs::Helper
+
+ def initialize(schema, output_dir:, template:)
+ @output_dir = output_dir
+ @template = template
+ @layout = Haml::Engine.new(File.read(template))
+ @parsed_schema = GraphQLDocs::Parser.new(schema, {}).parse
+ end
+
+ def render
+ contents = @layout.render(self)
+
+ write_file(contents)
+ end
+
+ private
+
+ def write_file(contents)
+ filename = File.join(@output_dir, 'index.md')
+
+ FileUtils.mkdir_p(@output_dir)
+ File.write(filename, contents)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/docs/templates/default.md.haml b/lib/gitlab/graphql/docs/templates/default.md.haml
new file mode 100644
index 00000000000..cc22d43ab4f
--- /dev/null
+++ b/lib/gitlab/graphql/docs/templates/default.md.haml
@@ -0,0 +1,25 @@
+-# haml-lint:disable UnnecessaryStringOutput
+
+= auto_generated_comment
+
+:plain
+ # GraphQL API Resources
+
+ This documentation is self-generated based on GitLab current GraphQL schema.
+
+ The API can be explored interactively using the [GraphiQL IDE](../index.md#graphiql).
+
+ ## Objects
+\
+- objects.each do |type|
+ - unless type[:fields].empty?
+ = "### #{type[:name]}"
+ \
+ ~ "| Name | Type | Description |"
+ ~ "| --- | ---- | ---------- |"
+ - type[:fields].each do |field|
+ = "| `#{field[:name]}` | #{render_field_type(field[:type][:info])} | #{field[:description]} |"
+ \
+
+
+
diff --git a/lib/gitlab/graphql/representation/submodule_tree_entry.rb b/lib/gitlab/graphql/representation/submodule_tree_entry.rb
new file mode 100644
index 00000000000..65716dff75d
--- /dev/null
+++ b/lib/gitlab/graphql/representation/submodule_tree_entry.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Representation
+ class SubmoduleTreeEntry < SimpleDelegator
+ class << self
+ def decorate(submodules, tree)
+ repository = tree.repository
+ submodule_links = Gitlab::SubmoduleLinks.new(repository)
+
+ submodules.map do |submodule|
+ self.new(submodule, submodule_links.for(submodule, tree.sha))
+ end
+ end
+ end
+
+ def initialize(submodule, submodule_links)
+ @submodule_links = submodule_links
+
+ super(submodule)
+ end
+
+ def web_url
+ @submodule_links.first
+ end
+
+ def tree_url
+ @submodule_links.last
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/hashed_storage/migrator.rb b/lib/gitlab/hashed_storage/migrator.rb
index 1f0deebea39..6a8e16f5a85 100644
--- a/lib/gitlab/hashed_storage/migrator.rb
+++ b/lib/gitlab/hashed_storage/migrator.rb
@@ -62,6 +62,7 @@ module Gitlab
# Flag a project to be migrated to Hashed Storage
#
# @param [Project] project that will be migrated
+ # rubocop:disable Gitlab/RailsLogger
def migrate(project)
Rails.logger.info "Starting storage migration of #{project.full_path} (ID=#{project.id})..."
@@ -69,10 +70,12 @@ module Gitlab
rescue => err
Rails.logger.error("#{err.message} migrating storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
end
+ # rubocop:enable Gitlab/RailsLogger
# Flag a project to be rolled-back to Legacy Storage
#
# @param [Project] project that will be rolled-back
+ # rubocop:disable Gitlab/RailsLogger
def rollback(project)
Rails.logger.info "Starting storage rollback of #{project.full_path} (ID=#{project.id})..."
@@ -80,6 +83,7 @@ module Gitlab
rescue => err
Rails.logger.error("#{err.message} rolling-back storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
end
+ # rubocop:enable Gitlab/RailsLogger
# Returns whether we have any pending storage migration
#
diff --git a/lib/gitlab/hashed_storage/rake_helper.rb b/lib/gitlab/hashed_storage/rake_helper.rb
index 87a31a37e3f..14727b03ce9 100644
--- a/lib/gitlab/hashed_storage/rake_helper.rb
+++ b/lib/gitlab/hashed_storage/rake_helper.rb
@@ -19,8 +19,12 @@ module Gitlab
ENV['ID_TO']
end
+ def self.using_ranges?
+ !range_from.nil? && !range_to.nil?
+ end
+
def self.range_single_item?
- !range_from.nil? && range_from == range_to
+ using_ranges? && range_from == range_to
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/health_checks/simple_abstract_check.rb b/lib/gitlab/health_checks/simple_abstract_check.rb
index 3588260d6eb..5a1e8c2a1dd 100644
--- a/lib/gitlab/health_checks/simple_abstract_check.rb
+++ b/lib/gitlab/health_checks/simple_abstract_check.rb
@@ -18,7 +18,7 @@ module Gitlab
def metrics
result, elapsed = with_timing(&method(:check))
- Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless successful?(result)
+ Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless successful?(result) # rubocop:disable Gitlab/RailsLogger
[
metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0),
metric("#{metric_prefix}_success", successful?(result) ? 1 : 0),
diff --git a/lib/gitlab/import/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/attribute_cleaner.rb b/lib/gitlab/import_export/attribute_cleaner.rb
index c28a1674018..b2fe9592c06 100644
--- a/lib/gitlab/import_export/attribute_cleaner.rb
+++ b/lib/gitlab/import_export/attribute_cleaner.rb
@@ -3,7 +3,7 @@
module Gitlab
module ImportExport
class AttributeCleaner
- ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
+ ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + %w[group_id commit_id]
PROHIBITED_REFERENCES = Regexp.union(/\Acached_markdown_version\Z/, /_id\Z/, /_html\Z/).freeze
def self.clean(*args)
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..ab19a509310 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -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/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb
index deb2f59f05f..0b534a5bafc 100644
--- a/lib/gitlab/import_export/merge_request_parser.rb
+++ b/lib/gitlab/import_export/merge_request_parser.rb
@@ -43,7 +43,7 @@ module Gitlab
target_ref = Gitlab::Git::BRANCH_REF_PREFIX + @merge_request.source_branch
unless @project.repository.fetch_source_branch!(@project.repository, @diff_head_sha, target_ref)
- Rails.logger.warn("Import/Export warning: Failed to create #{target_ref} for MR: #{@merge_request.iid}")
+ Rails.logger.warn("Import/Export warning: Failed to create #{target_ref} for MR: #{@merge_request.iid}") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 20caadb89c0..91fe4e5d074 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -20,7 +20,7 @@ module Gitlab
json = IO.read(@path)
@tree_hash = ActiveSupport::JSON.decode(json)
rescue => e
- Rails.logger.error("Import/Export error: #{e.message}")
+ Rails.logger.error("Import/Export error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
end
@@ -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 efd3f550a22..1b545b1d049 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -28,7 +28,7 @@ module Gitlab
links: 'Releases::Link',
metrics_setting: 'ProjectMetricsSetting' }.freeze
- USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze
+ USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id owner_id].freeze
PROJECT_REFERENCES = %w[project_id source_project_id target_project_id].freeze
@@ -78,6 +78,9 @@ module Gitlab
def create
return if unknown_service?
+ # Do not import legacy triggers
+ return if !Feature.enabled?(:use_legacy_pipeline_triggers, @project) && legacy_trigger?
+
setup_models
generate_imported_object
@@ -278,6 +281,10 @@ module Gitlab
!Object.const_defined?(parsed_relation_hash['type'])
end
+ def legacy_trigger?
+ @relation_name == 'Ci::Trigger' && @relation_hash['owner_id'].nil?
+ end
+
def find_or_create_object!
return relation_class.find_or_create_by(project_id: @project.id) if @relation_name == :project_feature
diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb
index 72f575db095..bea7a7cce65 100644
--- a/lib/gitlab/import_export/saver.rb
+++ b/lib/gitlab/import_export/saver.rb
@@ -18,7 +18,7 @@ module Gitlab
if compress_and_save
remove_export_path
- Rails.logger.info("Saved project export #{archive_file}")
+ Rails.logger.info("Saved project export #{archive_file}") # rubocop:disable Gitlab/RailsLogger
save_upload
else
diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb
index 6d978d00ea5..86ea7a30e69 100644
--- a/lib/gitlab/import_export/version_checker.rb
+++ b/lib/gitlab/import_export/version_checker.rb
@@ -36,7 +36,7 @@ module Gitlab
def different_version?(version)
Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
rescue => e
- Rails.logger.error("Import/Export error: #{e.message}")
+ Rails.logger.error("Import/Export error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
raise Gitlab::ImportExport::Error.new('Incorrect VERSION format')
end
end
diff --git a/lib/gitlab/kubernetes/helm/client_command.rb b/lib/gitlab/kubernetes/helm/client_command.rb
index 9940272a8bf..6ae68306a9b 100644
--- a/lib/gitlab/kubernetes/helm/client_command.rb
+++ b/lib/gitlab/kubernetes/helm/client_command.rb
@@ -13,15 +13,27 @@ module Gitlab
end
def wait_for_tiller_command
+ helm_check = ['helm', 'version', *optional_tls_flags].shelljoin
# This is necessary to give Tiller time to restart after upgrade.
# Ideally we'd be able to use --wait but cannot because of
# https://github.com/helm/helm/issues/4855
- 'for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done'
+ "for i in $(seq 1 30); do #{helm_check} && break; sleep 1s; echo \"Retrying ($i)...\"; done"
end
def repository_command
['helm', 'repo', 'add', name, repository].shelljoin if repository
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 e33ba9305ce..9744a5f3d8a 100644
--- a/lib/gitlab/kubernetes/helm/install_command.rb
+++ b/lib/gitlab/kubernetes/helm/install_command.rb
@@ -95,17 +95,6 @@ module Gitlab
['--version', version]
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/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index de14df56555..1350924cd76 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -59,6 +59,13 @@ module Gitlab
# RBAC methods delegates to the apis/rbac.authorization.k8s.io api
# group client
+ delegate :create_role,
+ :get_role,
+ :update_role,
+ to: :rbac_client
+
+ # RBAC methods delegates to the apis/rbac.authorization.k8s.io api
+ # group client
delegate :create_role_binding,
:get_role_binding,
:update_role_binding,
diff --git a/lib/gitlab/kubernetes/role.rb b/lib/gitlab/kubernetes/role.rb
new file mode 100644
index 00000000000..096f60f0372
--- /dev/null
+++ b/lib/gitlab/kubernetes/role.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ class Role
+ def initialize(name:, namespace:, rules:)
+ @name = name
+ @namespace = namespace
+ @rules = rules
+ end
+
+ def generate
+ ::Kubeclient::Resource.new(
+ metadata: { name: name, namespace: namespace },
+ rules: rules
+ )
+ end
+
+ private
+
+ attr_reader :name, :namespace, :rules
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/role_binding.rb b/lib/gitlab/kubernetes/role_binding.rb
index cb0cb42d007..0404fb4453c 100644
--- a/lib/gitlab/kubernetes/role_binding.rb
+++ b/lib/gitlab/kubernetes/role_binding.rb
@@ -3,9 +3,10 @@
module Gitlab
module Kubernetes
class RoleBinding
- def initialize(name:, role_name:, namespace:, service_account_name:)
+ def initialize(name:, role_name:, role_kind:, namespace:, service_account_name:)
@name = name
@role_name = role_name
+ @role_kind = role_kind
@namespace = namespace
@service_account_name = service_account_name
end
@@ -20,7 +21,7 @@ module Gitlab
private
- attr_reader :name, :role_name, :namespace, :service_account_name
+ attr_reader :name, :role_name, :role_kind, :namespace, :service_account_name
def metadata
{ name: name, namespace: namespace }
@@ -29,7 +30,7 @@ module Gitlab
def role_ref
{
apiGroup: 'rbac.authorization.k8s.io',
- kind: 'ClusterRole',
+ kind: role_kind,
name: role_name
}
end
diff --git a/lib/gitlab/lets_encrypt.rb b/lib/gitlab/lets_encrypt.rb
index cdf24f24647..08ad2ab91b0 100644
--- a/lib/gitlab/lets_encrypt.rb
+++ b/lib/gitlab/lets_encrypt.rb
@@ -2,15 +2,8 @@
module Gitlab
module LetsEncrypt
- def self.enabled?(pages_domain = nil)
- return false unless Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted
-
- return false unless Feature.enabled?(:pages_auto_ssl)
-
- # If no domain is passed, just check whether we're enabled globally
- return true unless pages_domain
-
- !!pages_domain.project && Feature.enabled?(:pages_auto_ssl_for_project, pages_domain.project)
+ def self.enabled?
+ Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted
end
end
end
diff --git a/lib/gitlab/markdown_cache/active_record/extension.rb b/lib/gitlab/markdown_cache/active_record/extension.rb
index f3abe631779..233d3bf1ac7 100644
--- a/lib/gitlab/markdown_cache/active_record/extension.rb
+++ b/lib/gitlab/markdown_cache/active_record/extension.rb
@@ -26,10 +26,6 @@ module Gitlab
attrs
end
- def changed_markdown_fields
- changed_attributes.keys.map(&:to_s) & cached_markdown_fields.markdown_fields.map(&:to_s)
- end
-
def write_markdown_field(field_name, value)
write_attribute(field_name, value)
end
diff --git a/lib/gitlab/markdown_cache/redis/extension.rb b/lib/gitlab/markdown_cache/redis/extension.rb
index 97fc23343b4..af3237f4ba6 100644
--- a/lib/gitlab/markdown_cache/redis/extension.rb
+++ b/lib/gitlab/markdown_cache/redis/extension.rb
@@ -36,8 +36,8 @@ module Gitlab
false
end
- def changed_markdown_fields
- []
+ def changed_attributes
+ {}
end
def cached_markdown
diff --git a/lib/gitlab/metrics/dashboard/url.rb b/lib/gitlab/metrics/dashboard/url.rb
new file mode 100644
index 00000000000..b197e7ca86b
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/url.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+# Manages url matching for metrics dashboards.
+module Gitlab
+ module Metrics
+ module Dashboard
+ class Url
+ class << self
+ # Matches urls for a metrics dashboard. This could be
+ # either the /metrics endpoint or the /metrics_dashboard
+ # endpoint.
+ #
+ # EX - https://<host>/<namespace>/<project>/environments/<env_id>/metrics
+ def regex
+ %r{
+ (?<url>
+ #{Regexp.escape(Gitlab.config.gitlab.url)}
+ \/#{Project.reference_pattern}
+ (?:\/\-)?
+ \/environments
+ \/(?<environment>\d+)
+ \/metrics
+ (?<query>
+ \?[a-z0-9_=-]+
+ (&[a-z0-9_=-]+)*
+ )?
+ (?<anchor>\#[a-z0-9_-]+)?
+ )
+ }x
+ end
+
+ # Builds a metrics dashboard url based on the passed in arguments
+ def build_dashboard_url(*args)
+ Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_environment_url(*args)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/samplers/base_sampler.rb b/lib/gitlab/metrics/samplers/base_sampler.rb
index 6a062e93f0f..d7d848d2833 100644
--- a/lib/gitlab/metrics/samplers/base_sampler.rb
+++ b/lib/gitlab/metrics/samplers/base_sampler.rb
@@ -19,7 +19,7 @@ module Gitlab
def safe_sample
sample
rescue => e
- Rails.logger.warn("#{self.class}: #{e}, stopping")
+ Rails.logger.warn("#{self.class}: #{e}, stopping") # rubocop:disable Gitlab/RailsLogger
stop
end
diff --git a/lib/gitlab/metrics/samplers/puma_sampler.rb b/lib/gitlab/metrics/samplers/puma_sampler.rb
index 25e40c70230..4e835f37c04 100644
--- a/lib/gitlab/metrics/samplers/puma_sampler.rb
+++ b/lib/gitlab/metrics/samplers/puma_sampler.rb
@@ -43,7 +43,7 @@ module Gitlab
def puma_stats
Puma.stats
rescue NoMethodError
- Rails.logger.info "PumaSampler: stats are not available yet, waiting for Puma to boot"
+ Rails.logger.info "PumaSampler: stats are not available yet, waiting for Puma to boot" # rubocop:disable Gitlab/RailsLogger
nil
end
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index eef802caabb..1e200db0baf 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -7,7 +7,7 @@ module Gitlab
module Samplers
class RubySampler < BaseSampler
def initialize(interval)
- metrics[:process_start_time_seconds].set(labels.merge(worker_label), Time.now.to_i)
+ metrics[:process_start_time_seconds].set(labels, Time.now.to_i)
super
end
@@ -30,18 +30,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.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,9 +50,9 @@ 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
@@ -75,22 +75,9 @@ module Gitlab
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/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
index 9af7e0afed4..355f938704e 100644
--- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb
+++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
@@ -54,7 +54,16 @@ module Gitlab
end
def unicorn_workers_count
- `pgrep -f '[u]nicorn_rails worker.+ #{Rails.root.to_s}'`.split.count
+ http_servers.sum(&:worker_processes) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ # Traversal of ObjectSpace is expensive, on fully loaded application
+ # it takes around 80ms. The instances of HttpServers are not a subject
+ # to change so we can cache the list of servers.
+ def http_servers
+ return [] unless defined?(::Unicorn::HttpServer)
+
+ @http_servers ||= ObjectSpace.each_object(::Unicorn::HttpServer).to_a
end
end
end
diff --git a/lib/gitlab/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/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 5c2f07b95e2..51f48095cb5 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -63,10 +63,6 @@ module Gitlab
def self.monotonic_time
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
end
-
- def self.clk_tck
- @clk_tck ||= `getconf CLK_TCK`.to_i
- end
end
end
end
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
index 817db12ac55..53e55269c6e 100644
--- a/lib/gitlab/middleware/read_only/controller.rb
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -25,7 +25,7 @@ module Gitlab
def call
if disallowed_request? && Gitlab::Database.read_only?
- Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation')
+ Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') # rubocop:disable Gitlab/RailsLogger
if json_request?
return [403, { 'Content-Type' => APPLICATION_JSON }, [{ 'message' => ERROR_MESSAGE }.to_json]]
diff --git a/lib/gitlab/omniauth_initializer.rb b/lib/gitlab/omniauth_initializer.rb
index 2a2083ebae0..ad1377a0892 100644
--- a/lib/gitlab/omniauth_initializer.rb
+++ b/lib/gitlab/omniauth_initializer.rb
@@ -52,6 +52,16 @@ module Gitlab
args[:strategy_class] = args[:strategy_class].constantize
end
+ # Providers that are known to depend on rack-oauth2, like those using
+ # Omniauth::Strategies::OpenIDConnect, need to be quirked so the
+ # client_auth_method argument value is passed as a symbol.
+ if (args[:strategy_class] == OmniAuth::Strategies::OpenIDConnect ||
+ args[:name] == 'openid_connect') &&
+ args[:client_auth_method].is_a?(String)
+
+ args[:client_auth_method] = args[:client_auth_method].to_sym
+ end
+
args
end
diff --git a/lib/gitlab/pages_client.rb b/lib/gitlab/pages_client.rb
index d74fdba2241..281eafb142f 100644
--- a/lib/gitlab/pages_client.rb
+++ b/lib/gitlab/pages_client.rb
@@ -110,7 +110,7 @@ module Gitlab
end
rescue Errno::EACCES => ex
# TODO stop rescuing this exception in GitLab 11.0 https://gitlab.com/gitlab-org/gitlab-ce/issues/45672
- Rails.logger.error("Could not write pages admin token file: #{ex}")
+ Rails.logger.error("Could not write pages admin token file: #{ex}") # rubocop:disable Gitlab/RailsLogger
rescue Errno::EEXIST
# Another process wrote the token file concurrently with us. Use their token, not ours.
end
diff --git a/lib/gitlab/patch/active_record_query_cache.rb b/lib/gitlab/patch/active_record_query_cache.rb
new file mode 100644
index 00000000000..71d66bdbe02
--- /dev/null
+++ b/lib/gitlab/patch/active_record_query_cache.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+# Fixes a bug where the query cache isn't aware of the shared
+# ActiveRecord connection used in tests
+# https://github.com/rails/rails/issues/36587
+
+# To be removed with https://gitlab.com/gitlab-org/gitlab-ce/issues/64413
+
+module Gitlab
+ module Patch
+ module ActiveRecordQueryCache
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def enable_query_cache!
+ @query_cache_enabled[connection_cache_key(current_thread)] = true
+ connection.enable_query_cache! if active_connection?
+ end
+
+ def disable_query_cache!
+ @query_cache_enabled.delete connection_cache_key(current_thread)
+ connection.disable_query_cache! if active_connection?
+ end
+
+ def query_cache_enabled
+ @query_cache_enabled[connection_cache_key(current_thread)]
+ end
+
+ def active_connection?
+ @thread_cached_conns[connection_cache_key(current_thread)]
+ end
+
+ private
+
+ def current_thread
+ @lock_thread || Thread.current
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/cache/map.rb b/lib/gitlab/phabricator_import/cache/map.rb
index fa8b37b20ca..6a2841b6a8e 100644
--- a/lib/gitlab/phabricator_import/cache/map.rb
+++ b/lib/gitlab/phabricator_import/cache/map.rb
@@ -9,9 +9,15 @@ module Gitlab
def get_gitlab_model(phabricator_id)
cached_info = get(phabricator_id)
- return unless cached_info[:classname] && cached_info[:database_id]
- cached_info[:classname].constantize.find_by_id(cached_info[:database_id])
+ if cached_info[:classname] && cached_info[:database_id]
+ object = cached_info[:classname].constantize.find_by_id(cached_info[:database_id])
+ else
+ object = yield if block_given?
+ set_gitlab_model(object, phabricator_id) if object
+ end
+
+ object
end
def set_gitlab_model(object, phabricator_id)
diff --git a/lib/gitlab/phabricator_import/conduit/user.rb b/lib/gitlab/phabricator_import/conduit/user.rb
new file mode 100644
index 00000000000..fc8c3f7cde9
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/user.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class User
+ MAX_PAGE_SIZE = 100
+
+ def initialize(phabricator_url:, api_token:)
+ @client = Client.new(phabricator_url, api_token)
+ end
+
+ def users(phids)
+ phids.each_slice(MAX_PAGE_SIZE).map { |limited_phids| get_page(limited_phids) }
+ end
+
+ private
+
+ def get_page(phids)
+ UsersResponse.new(get_users(phids))
+ end
+
+ def get_users(phids)
+ client.get('user.search',
+ params: { constraints: { phids: phids } })
+ end
+
+ attr_reader :client
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/conduit/users_response.rb b/lib/gitlab/phabricator_import/conduit/users_response.rb
new file mode 100644
index 00000000000..3dfb29a7be5
--- /dev/null
+++ b/lib/gitlab/phabricator_import/conduit/users_response.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ module Conduit
+ class UsersResponse
+ def initialize(conduit_response)
+ @conduit_response = conduit_response
+ end
+
+ def users
+ @users ||= conduit_response.data.map do |user_json|
+ Gitlab::PhabricatorImport::Representation::User.new(user_json)
+ end
+ end
+
+ private
+
+ attr_reader :conduit_response
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/issues/task_importer.rb b/lib/gitlab/phabricator_import/issues/task_importer.rb
index 40d4392cbc1..77ee11c7cdd 100644
--- a/lib/gitlab/phabricator_import/issues/task_importer.rb
+++ b/lib/gitlab/phabricator_import/issues/task_importer.rb
@@ -8,9 +8,7 @@ module Gitlab
end
def execute
- # TODO: get the user from the project namespace from the username loaded by Phab-id
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/60565
- issue.author = User.ghost
+ issue.author = user_finder.find(task.author_phid) || User.ghost
# TODO: Reformat the description with attachments, escaping accidental
# links and add attachments
@@ -19,6 +17,10 @@ module Gitlab
save!
+ if owner = user_finder.find(task.owner_phid)
+ issue.assignees << owner
+ end
+
issue
end
@@ -41,6 +43,10 @@ module Gitlab
project.issues.new
end
+ def user_finder
+ @issue_finder ||= Gitlab::PhabricatorImport::UserFinder.new(project, task.phids)
+ end
+
def find_issue_by_phabricator_id(phabricator_id)
object_map.get_gitlab_model(phabricator_id)
end
diff --git a/lib/gitlab/phabricator_import/representation/task.rb b/lib/gitlab/phabricator_import/representation/task.rb
index 6aedc71b626..ba93fb37a8e 100644
--- a/lib/gitlab/phabricator_import/representation/task.rb
+++ b/lib/gitlab/phabricator_import/representation/task.rb
@@ -11,6 +11,18 @@ module Gitlab
json['phid']
end
+ def author_phid
+ json['fields']['authorPHID']
+ end
+
+ def owner_phid
+ json['fields']['ownerPHID']
+ end
+
+ def phids
+ @phids ||= [author_phid, owner_phid]
+ end
+
def issue_attributes
@issue_attributes ||= {
title: issue_title,
diff --git a/lib/gitlab/phabricator_import/representation/user.rb b/lib/gitlab/phabricator_import/representation/user.rb
new file mode 100644
index 00000000000..7fd7cecc6ae
--- /dev/null
+++ b/lib/gitlab/phabricator_import/representation/user.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ module Representation
+ class User
+ def initialize(json)
+ @json = json
+ end
+
+ def phabricator_id
+ json['phid']
+ end
+
+ def username
+ json['fields']['username']
+ end
+
+ private
+
+ attr_reader :json
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/phabricator_import/user_finder.rb b/lib/gitlab/phabricator_import/user_finder.rb
new file mode 100644
index 00000000000..4b50431e0e0
--- /dev/null
+++ b/lib/gitlab/phabricator_import/user_finder.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module PhabricatorImport
+ class UserFinder
+ def initialize(project, phids)
+ @project, @phids = project, phids
+ @loaded_phids = Set.new
+ end
+
+ def find(phid)
+ found_user = object_map.get_gitlab_model(phid) do
+ find_user_for_phid(phid)
+ end
+
+ loaded_phids << phid
+
+ found_user
+ end
+
+ private
+
+ attr_reader :project, :phids, :loaded_phids
+
+ def object_map
+ @object_map ||= Gitlab::PhabricatorImport::Cache::Map.new(project)
+ end
+
+ def find_user_for_phid(phid)
+ phabricator_user = phabricator_users.find { |u| u.phabricator_id == phid }
+ return unless phabricator_user
+
+ project.authorized_users.find_by_username(phabricator_user.username)
+ end
+
+ def phabricator_users
+ @user_responses ||= client.users(users_to_request).flat_map(&:users)
+ end
+
+ def users_to_request
+ phids - loaded_phids.to_a
+ end
+
+ def client
+ @client ||=
+ Gitlab::PhabricatorImport::Conduit::User
+ .new(phabricator_url: project.import_data.data['phabricator_url'],
+ api_token: project.import_data.credentials[:api_token])
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/push_options.rb b/lib/gitlab/push_options.rb
index 3137676ba4b..b96590af08e 100644
--- a/lib/gitlab/push_options.rb
+++ b/lib/gitlab/push_options.rb
@@ -4,7 +4,12 @@ module Gitlab
class PushOptions
VALID_OPTIONS = HashWithIndifferentAccess.new({
merge_request: {
- keys: [:create, :merge_when_pipeline_succeeds, :target]
+ keys: [
+ :create,
+ :merge_when_pipeline_succeeds,
+ :remove_source_branch,
+ :target
+ ]
},
ci: {
keys: [:skip]
diff --git a/lib/gitlab/reference_counter.rb b/lib/gitlab/reference_counter.rb
index d2dbc6f5ef5..1c43de35816 100644
--- a/lib/gitlab/reference_counter.rb
+++ b/lib/gitlab/reference_counter.rb
@@ -22,6 +22,7 @@ module Gitlab
end
end
+ # rubocop:disable Gitlab/RailsLogger
def decrease
redis_cmd do |redis|
current_value = redis.decr(key)
@@ -32,6 +33,7 @@ module Gitlab
end
end
end
+ # rubocop:enable Gitlab/RailsLogger
private
@@ -39,7 +41,7 @@ module Gitlab
Gitlab::Redis::SharedState.with { |redis| yield(redis) }
true
rescue => e
- Rails.logger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}")
+ Rails.logger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
false
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 7a1a2eaf6c0..e43147a3f37 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -4,14 +4,6 @@ module Gitlab
module Regex
extend self
- def namespace_name_regex
- @namespace_name_regex ||= /\A[\p{Alnum}\p{Pd}_\. ]*\z/.freeze
- end
-
- def namespace_name_regex_message
- "can contain only letters, digits, '_', '.', dash and space."
- end
-
def project_name_regex
@project_name_regex ||= /\A[\p{Alnum}\u{00A9}-\u{1f9c0}_][\p{Alnum}\p{Pd}\u{00A9}-\u{1f9c0}_\. ]*\z/.freeze
end
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
index 931298b5117..e40c366ed02 100644
--- a/lib/gitlab/repository_cache_adapter.rb
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -145,7 +145,7 @@ module Gitlab
def expire_method_caches(methods)
methods.each do |name|
unless cached_methods.include?(name.to_sym)
- Rails.logger.error "Requested to expire non-existent method '#{name}' for Repository"
+ Rails.logger.error "Requested to expire non-existent method '#{name}' for Repository" # rubocop:disable Gitlab/RailsLogger
next
end
diff --git a/lib/gitlab/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/sanitizers/exif.rb b/lib/gitlab/sanitizers/exif.rb
index 0928ccdc324..bb4e4ce7bbc 100644
--- a/lib/gitlab/sanitizers/exif.rb
+++ b/lib/gitlab/sanitizers/exif.rb
@@ -48,7 +48,7 @@ module Gitlab
attr_reader :logger
- def initialize(logger: Rails.logger)
+ def initialize(logger: Rails.logger) # rubocop:disable Gitlab/RailsLogger
@logger = logger
end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 93182607616..0fa17b3f559 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -78,7 +78,7 @@ module Gitlab
true
rescue => err # Once the Rugged codes gets removes this can be improved
- Rails.logger.error("Failed to add repository #{storage}/#{disk_path}: #{err}")
+ Rails.logger.error("Failed to add repository #{storage}/#{disk_path}: #{err}") # rubocop:disable Gitlab/RailsLogger
false
end
@@ -153,7 +153,7 @@ module Gitlab
!!rm_directory(storage, "#{name}.git")
rescue ArgumentError => e
- Rails.logger.warn("Repository does not exist: #{e} at: #{name}.git")
+ Rails.logger.warn("Repository does not exist: #{e} at: #{name}.git") # rubocop:disable Gitlab/RailsLogger
false
end
@@ -238,7 +238,7 @@ module Gitlab
def remove_keys_not_found_in_db
return unless self.authorized_keys_enabled?
- Rails.logger.info("Removing keys not found in DB")
+ Rails.logger.info("Removing keys not found in DB") # rubocop:disable Gitlab/RailsLogger
batch_read_key_ids do |ids_in_file|
ids_in_file.uniq!
@@ -248,7 +248,7 @@ module Gitlab
ids_to_remove = ids_in_file - keys_in_db.pluck(:id)
ids_to_remove.each do |id|
- Rails.logger.info("Removing key-#{id} not found in DB")
+ Rails.logger.info("Removing key-#{id} not found in DB") # rubocop:disable Gitlab/RailsLogger
remove_key("key-#{id}")
end
end
@@ -368,7 +368,7 @@ module Gitlab
return true if status.zero?
- Rails.logger.error("gitlab-shell failed with error #{status}: #{output}")
+ Rails.logger.error("gitlab-shell failed with error #{status}: #{output}") # rubocop:disable Gitlab/RailsLogger
false
end
@@ -465,7 +465,7 @@ module Gitlab
end
def logger
- Rails.logger
+ Rails.logger # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/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/submodule_links.rb b/lib/gitlab/submodule_links.rb
new file mode 100644
index 00000000000..a6c0369d864
--- /dev/null
+++ b/lib/gitlab/submodule_links.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class SubmoduleLinks
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(repository)
+ @repository = repository
+ end
+
+ def for(submodule, sha)
+ submodule_url = submodule_url_for(sha)[submodule.path]
+ SubmoduleHelper.submodule_links_for_url(submodule.id, submodule_url, repository)
+ end
+
+ private
+
+ attr_reader :repository
+
+ def submodule_url_for(sha)
+ strong_memoize(:"submodule_links_for_#{sha}") do
+ repository.submodule_urls_for(sha)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 9a8df719827..f6b2e2acf16 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -18,7 +18,6 @@ module Gitlab
# enforce_sanitization - Raises error if URL includes any HTML/CSS/JS tags and argument is true.
#
# Returns an array with [<uri>, <original-hostname>].
- # rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/ParameterLists
def validate!(
url,
@@ -30,7 +29,6 @@ module Gitlab
enforce_user: false,
enforce_sanitization: false,
dns_rebind_protection: true)
- # rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/ParameterLists
return [nil, nil] if url.nil?
@@ -38,36 +36,31 @@ module Gitlab
# Param url can be a string, URI or Addressable::URI
uri = parse_url(url)
- validate_html_tags!(uri) if enforce_sanitization
+ validate_uri(
+ uri: uri,
+ schemes: schemes,
+ ports: ports,
+ enforce_sanitization: enforce_sanitization,
+ enforce_user: enforce_user,
+ ascii_only: ascii_only
+ )
hostname = uri.hostname
port = get_port(uri)
- unless internal?(uri)
- validate_scheme!(uri.scheme, schemes)
- validate_port!(port, ports) if ports.any?
- validate_user!(uri.user) if enforce_user
- validate_hostname!(hostname)
- validate_unicode_restriction!(uri) if ascii_only
- end
-
- begin
- addrs_info = Addrinfo.getaddrinfo(hostname, port, nil, :STREAM).map do |addr|
- addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
- end
- rescue SocketError
- return [uri, nil]
- end
+ address_info = get_address_info(hostname, port)
+ return [uri, nil] unless address_info
- protected_uri_with_hostname = enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection)
+ protected_uri_with_hostname = enforce_uri_hostname(address_info, 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_localhost!(addrs_info) unless allow_localhost
- validate_loopback!(addrs_info) unless allow_localhost
- validate_local_network!(addrs_info) unless allow_local_network
- validate_link_local!(addrs_info) unless allow_local_network
+ validate_local_request(
+ address_info: address_info,
+ allow_localhost: allow_localhost,
+ allow_local_network: allow_local_network
+ )
protected_uri_with_hostname
end
@@ -101,11 +94,44 @@ module Gitlab
[uri, hostname]
end
+ def validate_uri(uri:, schemes:, ports:, enforce_sanitization:, enforce_user:, ascii_only:)
+ validate_html_tags(uri) if enforce_sanitization
+
+ return if internal?(uri)
+
+ validate_scheme(uri.scheme, schemes)
+ validate_port(get_port(uri), ports) if ports.any?
+ validate_user(uri.user) if enforce_user
+ validate_hostname(uri.hostname)
+ validate_unicode_restriction(uri) if ascii_only
+ end
+
+ def get_address_info(hostname, port)
+ Addrinfo.getaddrinfo(hostname, port, nil, :STREAM).map do |addr|
+ addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
+ end
+ rescue SocketError
+ end
+
+ def validate_local_request(address_info:, allow_localhost:, allow_local_network:)
+ return if allow_local_network && allow_localhost
+
+ unless allow_localhost
+ validate_localhost(address_info)
+ validate_loopback(address_info)
+ end
+
+ unless allow_local_network
+ validate_local_network(address_info)
+ validate_link_local(address_info)
+ end
+ end
+
def get_port(uri)
uri.port || uri.default_port
end
- def validate_html_tags!(uri)
+ def validate_html_tags(uri)
uri_str = uri.to_s
sanitized_uri = ActionController::Base.helpers.sanitize(uri_str, tags: [])
if sanitized_uri != uri_str
@@ -125,7 +151,7 @@ module Gitlab
CGI.unescape(url.to_s) =~ /\n|\r/
end
- def validate_port!(port, ports)
+ def validate_port(port, ports)
return if port.blank?
# Only ports under 1024 are restricted
return if port >= 1024
@@ -134,20 +160,20 @@ module Gitlab
raise BlockedUrlError, "Only allowed ports are #{ports.join(', ')}, and any over 1024"
end
- def validate_scheme!(scheme, schemes)
+ def validate_scheme(scheme, schemes)
if scheme.blank? || (schemes.any? && !schemes.include?(scheme))
raise BlockedUrlError, "Only allowed schemes are #{schemes.join(', ')}"
end
end
- def validate_user!(value)
+ def validate_user(value)
return if value.blank?
return if value =~ /\A\p{Alnum}/
raise BlockedUrlError, "Username needs to start with an alphanumeric character"
end
- def validate_hostname!(value)
+ def validate_hostname(value)
return if value.blank?
return if IPAddress.valid?(value)
return if value =~ /\A\p{Alnum}/
@@ -155,13 +181,13 @@ module Gitlab
raise BlockedUrlError, "Hostname or IP address invalid"
end
- def validate_unicode_restriction!(uri)
+ def validate_unicode_restriction(uri)
return if uri.to_s.ascii_only?
raise BlockedUrlError, "URI must be ascii only #{uri.to_s.dump}"
end
- def validate_localhost!(addrs_info)
+ def validate_localhost(addrs_info)
local_ips = ["::", "0.0.0.0"]
local_ips.concat(Socket.ip_address_list.map(&:ip_address))
@@ -170,19 +196,19 @@ module Gitlab
raise BlockedUrlError, "Requests to localhost are not allowed"
end
- def validate_loopback!(addrs_info)
+ def validate_loopback(addrs_info)
return unless addrs_info.any? { |addr| addr.ipv4_loopback? || addr.ipv6_loopback? }
raise BlockedUrlError, "Requests to loopback addresses are not allowed"
end
- def validate_local_network!(addrs_info)
+ def validate_local_network(addrs_info)
return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? || addr.ipv6_unique_local? }
raise BlockedUrlError, "Requests to the local network are not allowed"
end
- def validate_link_local!(addrs_info)
+ def validate_link_local(addrs_info)
netmask = IPAddr.new('169.254.0.0/16')
return unless addrs_info.any? { |addr| addr.ipv6_linklocal? || netmask.include?(addr.ip_address) }
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 0180fe7fa71..db1086c9cae 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
@@ -128,10 +130,15 @@ module Gitlab
}
end
+ # @return [Hash<Symbol, Integer>]
def usage_counters
- {
- web_ide_commits: Gitlab::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]
end
def components_usage_data
diff --git a/lib/gitlab/usage_data_counters/redis_counter.rb b/lib/gitlab/usage_data_counters/redis_counter.rb
new file mode 100644
index 00000000000..75d5a75e3a4
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/redis_counter.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ module RedisCounter
+ 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(redis_counter_key)
+ Gitlab::Redis::SharedState.with { |redis| redis.get(redis_counter_key).to_i }
+ 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..c8b59a3160c
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/wiki_page_counter.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab::UsageDataCounters
+ class WikiPageCounter
+ extend RedisCounter
+
+ KNOWN_EVENTS = %w[create update delete].map(&:freeze).freeze
+
+ UnknownEvent = Class.new(StandardError)
+
+ class << self
+ # Each event gets a unique Redis key
+ def redis_key(event)
+ raise UnknownEvent, event unless KNOWN_EVENTS.include?(event.to_s)
+
+ "USAGE_WIKI_PAGES_#{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| ["wiki_pages_#{e}".to_sym, read(e)] }.to_h
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/web_ide_commits_counter.rb b/lib/gitlab/web_ide_commits_counter.rb
deleted file mode 100644
index 1cd9b5295b9..00000000000
--- a/lib/gitlab/web_ide_commits_counter.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module WebIdeCommitsCounter
- WEB_IDE_COMMITS_KEY = "WEB_IDE_COMMITS_COUNT".freeze
-
- class << self
- def increment
- Gitlab::Redis::SharedState.with { |redis| redis.incr(WEB_IDE_COMMITS_KEY) }
- end
-
- def total_count
- Gitlab::Redis::SharedState.with { |redis| redis.get(WEB_IDE_COMMITS_KEY).to_i }
- 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
new file mode 100644
index 00000000000..d9994898a08
--- /dev/null
+++ b/lib/gitlab/zoom_link_extractor.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# Detect links matching the following formats:
+# Zoom Start links: https://zoom.us/s/<meeting-id>
+# Zoom Join links: https://zoom.us/j/<meeting-id>
+# Personal Zoom links: https://zoom.us/my/<meeting-id>
+# Vanity Zoom links: https://gitlab.zoom.us/j/<meeting-id> (also /s and /my)
+
+module Gitlab
+ class ZoomLinkExtractor
+ ZOOM_REGEXP = %r{https://(?:[\w-]+\.)?zoom\.us/(?:s|j|my)/\S+}.freeze
+
+ def initialize(text)
+ @text = text.to_s
+ end
+
+ def links
+ @text.scan(ZOOM_REGEXP)
+ end
+ end
+end
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index 722e3e04d1c..eea7daa3d8e 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -41,7 +41,7 @@ module Mattermost
begin
yield self
rescue Errno::ECONNREFUSED => e
- Rails.logger.error(e.message + "\n" + e.backtrace.join("\n"))
+ Rails.logger.error(e.message + "\n" + e.backtrace.join("\n")) # rubocop:disable Gitlab/RailsLogger
raise Mattermost::NoSessionError
ensure
destroy
diff --git a/lib/microsoft_teams/notifier.rb b/lib/microsoft_teams/notifier.rb
index c7dec09ba6b..a7dcd322e27 100644
--- a/lib/microsoft_teams/notifier.rb
+++ b/lib/microsoft_teams/notifier.rb
@@ -20,7 +20,7 @@ module MicrosoftTeams
result = true if response
rescue Gitlab::HTTP::Error, StandardError => error
- Rails.logger.info("#{self.class.name}: Error while connecting to #{@webhook}: #{error.message}")
+ Rails.logger.info("#{self.class.name}: Error while connecting to #{@webhook}: #{error.message}") # rubocop:disable Gitlab/RailsLogger
end
result
diff --git a/lib/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/views/redis.rb b/lib/peek/views/redis_detailed.rb
index ad3c3c9fe01..12760c9b75e 100644
--- a/lib/peek/views/redis.rb
+++ b/lib/peek/views/redis_detailed.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'redis'
-require 'peek-redis'
module Gitlab
module Peek
@@ -36,9 +35,42 @@ end
module Peek
module Views
- module RedisDetailed
+ class RedisDetailed < View
+ REDACTED_MARKER = "<redacted>"
+
+ def key
+ 'redis'
+ end
+
def results
- super.merge(details: details)
+ {
+ calls: calls,
+ duration: formatted_duration,
+ details: details
+ }
+ end
+
+ def detail_store
+ ::Gitlab::SafeRequestStore['redis_call_details'] ||= []
+ end
+
+ private
+
+ def formatted_duration
+ ms = duration * 1000
+ if ms >= 1000
+ "%.2fms" % ms
+ else
+ "%.0fms" % ms
+ end
+ end
+
+ def duration
+ detail_store.map { |entry| entry[:duration] }.sum # rubocop:disable CodeReuse/ActiveRecord
+ end
+
+ def calls
+ detail_store.count
end
def details
@@ -47,20 +79,18 @@ module Peek
.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
- if cmd.length >= 2 && cmd.first =~ /set/i
- cmd[-1] = "<redacted>"
+ elsif cmd.length >= 3 && cmd.first =~ /set/i
+ cmd[2..-1] = REDACTED_MARKER
end
cmd.join(' ')
@@ -72,11 +102,3 @@ 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/rugged.rb b/lib/peek/views/rugged.rb
new file mode 100644
index 00000000000..7b0ab162b15
--- /dev/null
+++ b/lib/peek/views/rugged.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Peek
+ module Views
+ class Rugged < View
+ def duration
+ ::Gitlab::RuggedInstrumentation.query_time
+ end
+
+ def calls
+ ::Gitlab::RuggedInstrumentation.query_count
+ end
+
+ def results
+ return {} unless calls > 0
+
+ {
+ duration: formatted_duration,
+ calls: calls,
+ details: details
+ }
+ end
+
+ private
+
+ def details
+ ::Gitlab::RuggedInstrumentation.list_call_details
+ .sort { |a, b| b[:duration] <=> a[:duration] }
+ .map(&method(:format_call_details))
+ end
+
+ def format_call_details(call)
+ call.merge(duration: (call[:duration] * 1000).round(3),
+ args: format_args(call[:args]))
+ end
+
+ def format_args(args)
+ args.map do |arg|
+ # Needed to avoid infinite as_json calls
+ if arg.is_a?(Gitlab::Git::Repository)
+ arg.to_s
+ else
+ arg
+ end
+ end
+ end
+
+ def formatted_duration
+ ms = duration * 1000
+ if ms >= 1000
+ "%.2fms" % ms
+ else
+ "%.0fms" % ms
+ end
+ end
+ end
+ end
+end
diff --git a/lib/prometheus/pid_provider.rb b/lib/prometheus/pid_provider.rb
new file mode 100644
index 00000000000..c92522c73c5
--- /dev/null
+++ b/lib/prometheus/pid_provider.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'prometheus/client/support/unicorn'
+
+module Prometheus
+ module PidProvider
+ extend self
+
+ def worker_id
+ if Sidekiq.server?
+ 'sidekiq'
+ elsif defined?(Unicorn::Worker)
+ "unicorn_#{unicorn_worker_id}"
+ elsif defined?(::Puma)
+ "puma_#{puma_worker_id}"
+ else
+ "process_#{Process.pid}"
+ end
+ end
+
+ private
+
+ # This is not fully accurate as we don't really know if the nil returned
+ # is actually means we're on master or not.
+ # Follow up issue was created to address this problem and
+ # to introduce more structrured approach to a current process discovery:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/64740
+ def unicorn_worker_id
+ ::Prometheus::Client::Support::Unicorn.worker_id || 'master'
+ end
+
+ # See the comment for #unicorn_worker_id
+ def puma_worker_id
+ match = process_name.match(/cluster worker ([0-9]+):/)
+ match ? match[1] : 'master'
+ end
+
+ def process_name
+ $0
+ end
+ end
+end
diff --git a/lib/rspec_flaky/listener.rb b/lib/rspec_flaky/listener.rb
index 19cc0baa2d3..bf15130d17e 100644
--- a/lib/rspec_flaky/listener.rb
+++ b/lib/rspec_flaky/listener.rb
@@ -32,6 +32,7 @@ module RspecFlaky
flaky_examples[current_example.uid] = flaky_example
end
+ # rubocop:disable Gitlab/RailsLogger
def dump_summary(_)
RspecFlaky::Report.new(flaky_examples).write(RspecFlaky::Config.flaky_examples_report_path)
# write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path)
@@ -45,6 +46,7 @@ module RspecFlaky
# write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path)
end
end
+ # rubocop:enable Gitlab/RailsLogger
private
diff --git a/lib/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/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 105ef417df3..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
@@ -147,6 +199,7 @@ namespace :gitlab do
ENV['NICENESS'].presence
end
+ # rubocop:disable Gitlab/RailsLogger
def logger
return @logger if defined?(@logger)
@@ -159,5 +212,6 @@ namespace :gitlab do
Rails.logger
end
end
+ # rubocop:enable Gitlab/RailsLogger
end
end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 4e7a8adbef6..5f2ed5274e4 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -26,12 +26,9 @@ 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'
@@ -40,12 +37,8 @@ namespace :gitlab do
# 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'
diff --git a/lib/tasks/gitlab/features.rake b/lib/tasks/gitlab/features.rake
index d88bcca0819..9cf568c07fe 100644
--- a/lib/tasks/gitlab/features.rake
+++ b/lib/tasks/gitlab/features.rake
@@ -10,14 +10,22 @@ namespace :gitlab do
set_rugged_feature_flags(false)
puts 'All Rugged feature flags were disabled.'
end
+
+ task unset_rugged: :environment do
+ set_rugged_feature_flags(nil)
+ puts 'All Rugged feature flags were unset.'
+ end
end
def set_rugged_feature_flags(status)
Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
- if status
- Feature.enable(flag)
- else
+ case status
+ when nil
Feature.get(flag).remove
+ when true
+ Feature.enable(flag)
+ when false
+ Feature.disable(flag)
end
end
end
diff --git a/lib/tasks/gitlab/graphql.rake b/lib/tasks/gitlab/graphql.rake
new file mode 100644
index 00000000000..c53d55ceea2
--- /dev/null
+++ b/lib/tasks/gitlab/graphql.rake
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+return if Rails.env.production?
+
+namespace :gitlab do
+ OUTPUT_DIR = Rails.root.join("doc/api/graphql/reference").freeze
+ TEMPLATES_DIR = 'lib/gitlab/graphql/docs/templates/'.freeze
+
+ namespace :graphql do
+ desc 'GitLab | Generate GraphQL docs'
+ task compile_docs: :environment do
+ renderer = Gitlab::Graphql::Docs::Renderer.new(GitlabSchema.graphql_definition, render_options)
+
+ renderer.render
+
+ puts "Documentation compiled."
+ end
+ end
+end
+
+def render_options
+ {
+ output_dir: OUTPUT_DIR,
+ template: Rails.root.join(TEMPLATES_DIR, 'default.md.haml')
+ }
+end
diff --git a/lib/tasks/gitlab/seed.rake b/lib/tasks/gitlab/seed.rake
index 155ba979b36..d76e38b73b5 100644
--- a/lib/tasks/gitlab/seed.rake
+++ b/lib/tasks/gitlab/seed.rake
@@ -1,7 +1,9 @@
namespace :gitlab do
namespace :seed do
desc "GitLab | Seed | Seeds issues"
- task :issues, [:project_full_path] => :environment do |t, args|
+ task :issues, [:project_full_path, :backfill_weeks, :average_issues_per_week] => :environment do |t, args|
+ args.with_defaults(backfill_weeks: 5, average_issues_per_week: 2)
+
projects =
if args.project_full_path
project = Project.find_by_full_path(args.project_full_path)
@@ -26,7 +28,8 @@ namespace :gitlab do
projects.each do |project|
puts "\nSeeding issues for the '#{project.full_path}' project"
seeder = Quality::Seeders::Issues.new(project: project)
- issues_created = seeder.seed(backfill_weeks: 5, average_issues_per_week: 2)
+ issues_created = seeder.seed(backfill_weeks: args.backfill_weeks.to_i,
+ average_issues_per_week: args.average_issues_per_week.to_i)
puts "\n#{issues_created} issues created!"
end
end
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index e876b23d43f..e763de682f8 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
diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake
index 954f827f716..ccc96b7edfb 100644
--- a/lib/tasks/gitlab/storage.rake
+++ b/lib/tasks/gitlab/storage.rake
@@ -3,50 +3,44 @@ namespace :gitlab do
desc 'GitLab | Storage | Migrate existing projects to Hashed Storage'
task migrate_to_hashed: :environment do
if Gitlab::Database.read_only?
- warn 'This task requires database write access. Exiting.'
-
- next
+ abort 'This task requires database write access. Exiting.'
end
storage_migrator = Gitlab::HashedStorage::Migrator.new
helper = Gitlab::HashedStorage::RakeHelper
if storage_migrator.rollback_pending?
- warn "There is already a rollback operation in progress, " \
+ abort "There is already a rollback operation in progress, " \
"running a migration at the same time may have unexpected consequences."
-
- next
end
if helper.range_single_item?
project = Project.with_unmigrated_storage.find_by(id: helper.range_from)
unless project
- warn "There are no projects requiring storage migration with ID=#{helper.range_from}"
-
- next
+ abort "There are no projects requiring storage migration with ID=#{helper.range_from}"
end
puts "Enqueueing storage migration of #{project.full_path} (ID=#{project.id})..."
storage_migrator.migrate(project)
+ else
+ legacy_projects_count = if helper.using_ranges?
+ Project.with_unmigrated_storage.id_in(helper.range_from..helper.range_to).count
+ else
+ Project.with_unmigrated_storage.count
+ end
+
+ if legacy_projects_count == 0
+ abort 'There are no projects requiring storage migration. Nothing to do!'
+ end
- next
- end
-
- legacy_projects_count = Project.with_unmigrated_storage.count
-
- if legacy_projects_count == 0
- warn 'There are no projects requiring storage migration. Nothing to do!'
-
- next
- end
-
- print "Enqueuing migration of #{legacy_projects_count} projects in batches of #{helper.batch_size}"
+ print "Enqueuing migration of #{legacy_projects_count} projects in batches of #{helper.batch_size}"
- helper.project_id_batches_migration do |start, finish|
- storage_migrator.bulk_schedule_migration(start: start, finish: finish)
+ helper.project_id_batches_migration do |start, finish|
+ storage_migrator.bulk_schedule_migration(start: start, finish: finish)
- print '.'
+ print '.'
+ end
end
puts ' Done!'
@@ -55,50 +49,44 @@ namespace :gitlab do
desc 'GitLab | Storage | Rollback existing projects to Legacy Storage'
task rollback_to_legacy: :environment do
if Gitlab::Database.read_only?
- warn 'This task requires database write access. Exiting.'
-
- next
+ abort 'This task requires database write access. Exiting.'
end
storage_migrator = Gitlab::HashedStorage::Migrator.new
helper = Gitlab::HashedStorage::RakeHelper
if storage_migrator.migration_pending?
- warn "There is already a migration operation in progress, " \
+ abort "There is already a migration operation in progress, " \
"running a rollback at the same time may have unexpected consequences."
-
- next
end
if helper.range_single_item?
project = Project.with_storage_feature(:repository).find_by(id: helper.range_from)
unless project
- warn "There are no projects that can be rolledback with ID=#{helper.range_from}"
-
- next
+ abort "There are no projects that can be rolledback with ID=#{helper.range_from}"
end
puts "Enqueueing storage rollback of #{project.full_path} (ID=#{project.id})..."
storage_migrator.rollback(project)
+ else
+ hashed_projects_count = if helper.using_ranges?
+ Project.with_storage_feature(:repository).id_in(helper.range_from..helper.range_to).count
+ else
+ Project.with_storage_feature(:repository).count
+ end
+
+ if hashed_projects_count == 0
+ abort 'There are no projects that can have storage rolledback. Nothing to do!'
+ end
- next
- end
-
- hashed_projects_count = Project.with_storage_feature(:repository).count
-
- if hashed_projects_count == 0
- warn 'There are no projects that can have storage rolledback. Nothing to do!'
-
- next
- end
-
- print "Enqueuing rollback of #{hashed_projects_count} projects in batches of #{helper.batch_size}"
+ print "Enqueuing rollback of #{hashed_projects_count} projects in batches of #{helper.batch_size}"
- helper.project_id_batches_rollback do |start, finish|
- storage_migrator.bulk_schedule_rollback(start: start, finish: finish)
+ helper.project_id_batches_rollback do |start, finish|
+ storage_migrator.bulk_schedule_rollback(start: start, finish: finish)
- print '.'
+ print '.'
+ end
end
puts ' Done!'
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/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po
index 7ccdfa282e8..8e8a678fb49 100644
--- a/locale/ar_SA/gitlab.po
+++ b/locale/ar_SA/gitlab.po
@@ -16935,7 +16935,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 9e24b950cdd..6ac32a51c4a 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/bn_BD/gitlab.po b/locale/bn_BD/gitlab.po
index c7c0695b580..ed6daeb61cf 100644
--- a/locale/bn_BD/gitlab.po
+++ b/locale/bn_BD/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/bn_IN/gitlab.po b/locale/bn_IN/gitlab.po
index 56c5a3b3704..82a88455eb6 100644
--- a/locale/bn_IN/gitlab.po
+++ b/locale/bn_IN/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po
index c0b4aea4a99..7f85efb992b 100644
--- a/locale/ca_ES/gitlab.po
+++ b/locale/ca_ES/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po
index 1184a326ede..3e51d44aea9 100644
--- a/locale/cs_CZ/gitlab.po
+++ b/locale/cs_CZ/gitlab.po
@@ -16777,7 +16777,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/cy_GB/gitlab.po b/locale/cy_GB/gitlab.po
index 019dcd25e72..ac295b22014 100644
--- a/locale/cy_GB/gitlab.po
+++ b/locale/cy_GB/gitlab.po
@@ -16935,7 +16935,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po
index f30fbc0806c..697d555d4f5 100644
--- a/locale/da_DK/gitlab.po
+++ b/locale/da_DK/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index 7faeed34e59..d8355c1a73b 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr "nicht verfügbar"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/el_GR/gitlab.po b/locale/el_GR/gitlab.po
index 2ac09933c10..02262e56399 100644
--- a/locale/el_GR/gitlab.po
+++ b/locale/el_GR/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index df88e81f3d3..efac588a37b 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 616ef854a7d..d29d12c7f19 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr "debe ser mayor que la fecha de inicio"
msgid "n/a"
msgstr "n/a"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr "el intervalo debe estar comprendido entre 10 minutos y 1 mes"
msgid "new merge request"
diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po
index c7473191a42..e8cdb077a90 100644
--- a/locale/et_EE/gitlab.po
+++ b/locale/et_EE/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po
index be757241241..0ed26efcd80 100644
--- a/locale/fil_PH/gitlab.po
+++ b/locale/fil_PH/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 9b938ee38e6..e0945260d76 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr "non disponible"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 589f107765f..95b1d364d1c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -124,9 +124,6 @@ msgstr ""
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
-msgid "%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS"
-msgstr ""
-
msgid "%{count} more"
msgstr ""
@@ -141,9 +138,15 @@ msgstr[1] ""
msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
msgstr ""
+msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
+msgstr ""
+
msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
msgstr ""
+msgid "%{edit_in_new_fork_notice} Try to upload a file again."
+msgstr ""
+
msgid "%{filePath} deleted"
msgstr ""
@@ -174,9 +177,6 @@ msgstr ""
msgid "%{level_name} is not allowed since the fork source project has lower visibility."
msgstr ""
-msgid "%{level_name} visibility has been restricted by the administrator."
-msgstr ""
-
msgid "%{link_start}Read more%{link_end} about role permissions"
msgstr ""
@@ -710,6 +710,9 @@ msgstr ""
msgid "Add to review"
msgstr ""
+msgid "Add to tree"
+msgstr ""
+
msgid "Add user(s) to the group:"
msgstr ""
@@ -2984,6 +2987,12 @@ msgstr ""
msgid "ContainerRegistry|Container Registry"
msgstr ""
+msgid "ContainerRegistry|Copy build command to clipboard"
+msgstr ""
+
+msgid "ContainerRegistry|Copy push command to clipboard"
+msgstr ""
+
msgid "ContainerRegistry|Docker connection error"
msgstr ""
@@ -3017,13 +3026,13 @@ msgstr ""
msgid "ContainerRegistry|There are no container images stored for this project"
msgstr ""
-msgid "ContainerRegistry|We are having trouble connecting to Docker, which could be due to an issue with your project name or path. For more information, please review the %{docLinkStart}Container Registry documentation%{docLinkEnd}."
+msgid "ContainerRegistry|We are having trouble connecting to Docker, which could be due to an issue with your project name or path. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
-msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. Learn more about the %{docLinkStart}Container Registry%{docLinkEnd}."
+msgid "ContainerRegistry|With the Container Registry, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
-msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images. Learn more about the %{docLinkStart}Container Registry%{docLinkEnd}."
+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 the image <b>%{title}</b>. This will delete the image and all tags pointing to this image."
@@ -3266,6 +3275,9 @@ msgstr ""
msgid "Create new..."
msgstr ""
+msgid "Create project"
+msgstr ""
+
msgid "Create project label"
msgstr ""
@@ -4965,6 +4977,9 @@ msgstr ""
msgid "Gitea Import"
msgstr ""
+msgid "Gitlab Pages"
+msgstr ""
+
msgid "Given access %{time_ago}"
msgstr ""
@@ -5025,6 +5040,9 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar"
+msgstr ""
+
msgid "Gravatar enabled"
msgstr ""
@@ -5318,6 +5336,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 ""
@@ -5711,6 +5732,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 ""
@@ -5939,6 +5975,9 @@ 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"
msgstr ""
+msgid "LDAP"
+msgstr ""
+
msgid "LFS"
msgstr ""
@@ -6755,6 +6794,9 @@ msgstr ""
msgid "New Pipeline Schedule"
msgstr ""
+msgid "New Project"
+msgstr ""
+
msgid "New Snippet"
msgstr ""
@@ -6884,6 +6926,9 @@ msgstr ""
msgid "No data found"
msgstr ""
+msgid "No data to display"
+msgstr ""
+
msgid "No details available"
msgstr ""
@@ -7118,6 +7163,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 ""
@@ -7225,6 +7273,9 @@ msgstr ""
msgid "Other Labels"
msgstr ""
+msgid "Other visibility settings have been disabled by the administrator."
+msgstr ""
+
msgid "Outbound requests"
msgstr ""
@@ -7240,6 +7291,9 @@ msgstr ""
msgid "Owner"
msgstr ""
+msgid "Packages"
+msgstr ""
+
msgid "Page not found"
msgstr ""
@@ -8428,6 +8482,54 @@ msgstr ""
msgid "ProjectsDropdown|This feature requires browser localStorage support"
msgstr ""
+msgid "ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository."
+msgstr ""
+
+msgid "ProjectsNew|Blank"
+msgstr ""
+
+msgid "ProjectsNew|Blank project"
+msgstr ""
+
+msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
+msgstr ""
+
+msgid "ProjectsNew|Create from template"
+msgstr ""
+
+msgid "ProjectsNew|Creating project & repository."
+msgstr ""
+
+msgid "ProjectsNew|Description format"
+msgstr ""
+
+msgid "ProjectsNew|Import"
+msgstr ""
+
+msgid "ProjectsNew|Import project"
+msgstr ""
+
+msgid "ProjectsNew|Initialize repository with a README"
+msgstr ""
+
+msgid "ProjectsNew|No import options available"
+msgstr ""
+
+msgid "ProjectsNew|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
+msgstr ""
+
+msgid "ProjectsNew|Template"
+msgstr ""
+
+msgid "ProjectsNew|Visibility Level"
+msgstr ""
+
+msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
+msgstr ""
+
msgid "PrometheusService|%{exporters} with %{metrics} were found"
msgstr ""
@@ -8632,6 +8734,9 @@ msgstr ""
msgid "Receive notifications about your own activity"
msgstr ""
+msgid "Recent"
+msgstr ""
+
msgid "Recent Project Activity"
msgstr ""
@@ -8685,9 +8790,6 @@ msgstr ""
msgid "Register with two-factor app"
msgstr ""
-msgid "Registry"
-msgstr ""
-
msgid "Related Deployed Jobs"
msgstr ""
@@ -8808,6 +8910,9 @@ msgstr ""
msgid "Replace all label(s)"
msgstr ""
+msgid "Reply by email"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8877,6 +8982,9 @@ msgstr ""
msgid "Repository storage"
msgstr ""
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgstr ""
+
msgid "Request Access"
msgstr ""
@@ -9613,6 +9721,9 @@ msgstr ""
msgid "Sign out"
msgstr ""
+msgid "Sign up"
+msgstr ""
+
msgid "Sign-in restrictions"
msgstr ""
@@ -9673,6 +9784,9 @@ 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 ""
+msgid "Someone edited the issue at the same time you did. Please check out %{linkStart}the issue%{linkEnd} and make sure your changes will not unintentionally remove theirs."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -10045,6 +10159,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 ""
@@ -11224,10 +11350,10 @@ msgstr ""
msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
msgstr ""
-msgid "To protect this issues confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private."
+msgid "To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private."
msgstr ""
-msgid "To protect this issues confidentiality, a private fork of this project was selected."
+msgid "To protect this issue's confidentiality, a private fork of this project was selected."
msgstr ""
msgid "To see all the user's personal access tokens you must impersonate them first."
@@ -11920,6 +12046,9 @@ msgstr ""
msgid "Visibility level:"
msgstr ""
+msgid "Visibility settings have been disabled by the administrator."
+msgstr ""
+
msgid "Visibility, project features, permissions"
msgstr ""
@@ -12453,6 +12582,9 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request."
+msgstr ""
+
msgid "You're not allowed to make changes to this project directly. A fork of this project has been created that you can make changes in, so you can submit a merge request."
msgstr ""
@@ -12692,6 +12824,15 @@ msgstr ""
msgid "encrypted: needs to be a :required, :optional or :migrating!"
msgstr ""
+msgid "entries cannot be blank"
+msgstr ""
+
+msgid "entries cannot be larger than 255 characters"
+msgstr ""
+
+msgid "entries cannot contain HTML tags"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -13042,7 +13183,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
@@ -13194,6 +13335,9 @@ msgstr ""
msgid "triggered"
msgstr ""
+msgid "unicode domains should use IDNA encoding"
+msgstr ""
+
msgid "updated"
msgstr ""
diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po
index 50d4d42f36a..cfa68fccd1e 100644
--- a/locale/gl_ES/gitlab.po
+++ b/locale/gl_ES/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po
index dc6ff543726..7e76e201e2b 100644
--- a/locale/he_IL/gitlab.po
+++ b/locale/he_IL/gitlab.po
@@ -16777,7 +16777,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/hi_IN/gitlab.po b/locale/hi_IN/gitlab.po
index 126759c5828..c7f261878b2 100644
--- a/locale/hi_IN/gitlab.po
+++ b/locale/hi_IN/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/hr_HR/gitlab.po b/locale/hr_HR/gitlab.po
index 1f3242a6731..cac5c0a35cd 100644
--- a/locale/hr_HR/gitlab.po
+++ b/locale/hr_HR/gitlab.po
@@ -16698,7 +16698,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/hu_HU/gitlab.po b/locale/hu_HU/gitlab.po
index 142845c24a7..4ae0ab94c31 100644
--- a/locale/hu_HU/gitlab.po
+++ b/locale/hu_HU/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po
index be6c29d933c..12578ba567c 100644
--- a/locale/id_ID/gitlab.po
+++ b/locale/id_ID/gitlab.po
@@ -16540,7 +16540,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index 9b3ec180972..26d2a6c9be7 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index cc27cb06364..8a05c69ca85 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -16540,7 +16540,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/ka_GE/gitlab.po b/locale/ka_GE/gitlab.po
index f6d768828ca..202f4a1ce28 100644
--- a/locale/ka_GE/gitlab.po
+++ b/locale/ka_GE/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index 6138874ac7d..955221059f3 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -16540,7 +16540,7 @@ msgstr ""
msgid "n/a"
msgstr "해당 없음"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/mn_MN/gitlab.po b/locale/mn_MN/gitlab.po
index 11bb2a40d14..12cad4d8ba7 100644
--- a/locale/mn_MN/gitlab.po
+++ b/locale/mn_MN/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/nb_NO/gitlab.po b/locale/nb_NO/gitlab.po
index 7f150c167ee..8cac58c66bd 100644
--- a/locale/nb_NO/gitlab.po
+++ b/locale/nb_NO/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po
index faa40d15de9..24f3c7d8527 100644
--- a/locale/nl_NL/gitlab.po
+++ b/locale/nl_NL/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/pa_IN/gitlab.po b/locale/pa_IN/gitlab.po
index 723f2d4cb22..7120181643f 100644
--- a/locale/pa_IN/gitlab.po
+++ b/locale/pa_IN/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po
index e3b257d8c16..a8ab402925b 100644
--- a/locale/pl_PL/gitlab.po
+++ b/locale/pl_PL/gitlab.po
@@ -16777,7 +16777,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index 0a1e2508d66..750b4987ed3 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr "n/a"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/pt_PT/gitlab.po b/locale/pt_PT/gitlab.po
index f9174152adf..57cb6132b5f 100644
--- a/locale/pt_PT/gitlab.po
+++ b/locale/pt_PT/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po
index 71e8f9b199d..f3f04dd449b 100644
--- a/locale/ro_RO/gitlab.po
+++ b/locale/ro_RO/gitlab.po
@@ -16698,7 +16698,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index bb9e59a36a4..2ca9e319998 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -16777,7 +16777,7 @@ msgstr ""
msgid "n/a"
msgstr "н/д"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/sk_SK/gitlab.po b/locale/sk_SK/gitlab.po
index 0b014f91082..68e64991e06 100644
--- a/locale/sk_SK/gitlab.po
+++ b/locale/sk_SK/gitlab.po
@@ -16777,7 +16777,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po
index 2e43589b1fc..436c7e6034f 100644
--- a/locale/sq_AL/gitlab.po
+++ b/locale/sq_AL/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/sr_CS/gitlab.po b/locale/sr_CS/gitlab.po
index 34ba686fc45..02ac3e67563 100644
--- a/locale/sr_CS/gitlab.po
+++ b/locale/sr_CS/gitlab.po
@@ -16698,7 +16698,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/sr_SP/gitlab.po b/locale/sr_SP/gitlab.po
index cefbe0910c4..9b22d51e687 100644
--- a/locale/sr_SP/gitlab.po
+++ b/locale/sr_SP/gitlab.po
@@ -16698,7 +16698,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/sv_SE/gitlab.po b/locale/sv_SE/gitlab.po
index 33c75c49a0f..b39f28cda0e 100644
--- a/locale/sv_SE/gitlab.po
+++ b/locale/sv_SE/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/sw_KE/gitlab.po b/locale/sw_KE/gitlab.po
index 63860064d3c..bc2a228362c 100644
--- a/locale/sw_KE/gitlab.po
+++ b/locale/sw_KE/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po
index 3b552a3cbda..b801b5a93f4 100644
--- a/locale/tr_TR/gitlab.po
+++ b/locale/tr_TR/gitlab.po
@@ -16619,7 +16619,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr "10 dakika ile 1 ay arasında olması gerekiyor"
msgid "new merge request"
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index ee9d9bc0fee..b9056ca8ae1 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -16777,7 +16777,7 @@ msgstr "повинна бути пізніша за дату початку"
msgid "n/a"
msgstr "н/д"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr "має бути між 10 хвилинами та 1 місяцем"
msgid "new merge request"
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index bfa6064303c..ba86aa74c64 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -16540,7 +16540,7 @@ msgstr "必须大于开始日期"
msgid "n/a"
msgstr "不适用"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr "需要在10分钟到1个月之间"
msgid "new merge request"
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 541978fd726..d5ead45d34f 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -16540,7 +16540,7 @@ msgstr ""
msgid "n/a"
msgstr ""
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index 22de348bdaa..308c1cfd9fd 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -16540,7 +16540,7 @@ msgstr ""
msgid "n/a"
msgstr "未知"
-msgid "needs to be beetween 10 minutes and 1 month"
+msgid "needs to be between 10 minutes and 1 month"
msgstr ""
msgid "new merge request"
diff --git a/package.json b/package.json
index eb7293e6b50..dce20505fb0 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,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",
@@ -38,7 +38,7 @@
"@babel/preset-env": "^7.4.4",
"@gitlab/csslab": "^1.9.0",
"@gitlab/svgs": "^1.67.0",
- "@gitlab/ui": "^5.3.2",
+ "@gitlab/ui": "^5.7.1",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
@@ -96,7 +96,7 @@
"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",
@@ -117,7 +117,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,7 +137,7 @@
"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",
@@ -191,9 +190,10 @@
"pixelmatch": "^4.0.2",
"postcss": "^7.0.14",
"prettier": "1.18.2",
- "stylelint": "^9.10.1",
- "stylelint-config-recommended": "^2.1.0",
- "stylelint-scss": "^3.5.4",
+ "readdir-enhanced": "^2.2.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/.rspec_parallel b/qa/.rspec_parallel
new file mode 100644
index 00000000000..e5927927eaa
--- /dev/null
+++ b/qa/.rspec_parallel
@@ -0,0 +1,5 @@
+--color
+--format documentation
+--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
+--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
+--require spec_helper
diff --git a/qa/Gemfile b/qa/Gemfile
index 12994b85322..53e7cc497e2 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -1,5 +1,6 @@
source 'https://rubygems.org'
+gem 'gitlab-qa'
gem 'pry-byebug', '~> 3.5.1', platform: :mri
gem 'capybara', '~> 2.16.1'
gem 'capybara-screenshot', '~> 1.0.18'
@@ -9,5 +10,7 @@ gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13'
gem 'nokogiri', '~> 1.10.3'
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 6b0635ed0e2..7d19366f83b 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -35,6 +35,7 @@ GEM
faker (1.9.3)
i18n (>= 0.7)
ffi (1.9.25)
+ gitlab-qa (4.0.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
i18n (0.9.1)
@@ -53,6 +54,9 @@ GEM
netrc (0.11.0)
nokogiri (1.10.3)
mini_portile2 (~> 2.4.0)
+ parallel (1.17.0)
+ parallel_tests (2.29.0)
+ parallel
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@@ -83,6 +87,8 @@ 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)
@@ -104,12 +110,15 @@ DEPENDENCIES
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
faker (~> 1.6, >= 1.6.6)
+ gitlab-qa
knapsack (~> 1.17)
nokogiri (~> 1.10.3)
+ parallel_tests (~> 2.29)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
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 124a79a36b4..bab19665dac 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -75,14 +75,14 @@ You can also supply specific tests to run as another parameter. For example, to
run the repository-related specs, you can execute:
```
-bundle exec bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/repository
+bundle exec bin/qa Test::Instance::All http://localhost:3000 -- qa/specs/features/browser_ui/3_create/repository
```
Since the arguments would be passed to `rspec`, you could use all `rspec`
options there. For example, passing `--backtrace` and also line number:
```
-bundle exec bin/qa Test::Instance::All http://localhost -- qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb:6 --backtrace
+bundle exec bin/qa Test::Instance::All http://localhost:3000 -- qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb:6 --backtrace
```
Note that the separator `--` is required; all subsequent options will be
@@ -140,7 +140,7 @@ tests that are expected to fail while a fix is in progress (similar to how
can be used).
```
-bundle exec bin/qa Test::Instance::All http://localhost -- --tag quarantine
+bundle exec bin/qa Test::Instance::All http://localhost:3000 -- --tag quarantine
```
If `quarantine` is used with other tags, tests will only be run if they have at
@@ -159,7 +159,7 @@ option `--enable-feature FEATURE_FLAG`. For example, to enable the feature flag
that enforces Gitaly request limits, you would use the command:
```
-bundle exec bin/qa Test::Instance::All http://localhost --enable-feature gitaly_enforce_requests_limits
+bundle exec bin/qa Test::Instance::All http://localhost:3000 --enable-feature gitaly_enforce_requests_limits
```
This will instruct the QA framework to enable the `gitaly_enforce_requests_limits`
diff --git a/qa/qa.rb b/qa/qa.rb
index 10d44b6f6d9..be73776425b 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -360,6 +360,7 @@ module QA
module Specs
autoload :Config, 'qa/specs/config'
autoload :Runner, 'qa/specs/runner'
+ autoload :ParallelRunner, 'qa/specs/parallel_runner'
module Helpers
autoload :Quarantine, 'qa/specs/helpers/quarantine'
diff --git a/qa/qa/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/base.rb b/qa/qa/page/base.rb
index d0fe2987b0a..45496c6b245 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -25,19 +25,10 @@ module QA
end
end
- def retry_until(max_attempts: 3, reload: false)
- attempts = 0
-
- while attempts < max_attempts
- result = yield
- return result if result
-
- refresh if reload
-
- attempts += 1
+ def retry_until(max_attempts: 3, reload: false, sleep_interval: 0)
+ QA::Support::Retrier.retry_until(max_attempts: max_attempts, reload_page: (reload && self), sleep_interval: sleep_interval) do
+ yield
end
-
- false
end
def retry_on_exception(max_attempts: 3, reload: false, sleep_interval: 0.5)
@@ -190,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 07e191f1c9b..fe324574f4d 100644
--- a/qa/qa/page/component/note.rb
+++ b/qa/qa/page/component/note.rb
@@ -10,6 +10,10 @@ module QA
element :discussion_option
end
+ base.view 'app/assets/javascripts/notes/components/note_actions.vue' do
+ element :note_edit_button
+ end
+
base.view 'app/assets/javascripts/notes/components/note_form.vue' do
element :reply_input
element :reply_comment_button
@@ -49,6 +53,12 @@ module QA
def expand_replies
click_element :expand_replies
end
+
+ def edit_comment(text)
+ click_element :note_edit_button
+ fill_element :reply_input, text
+ click_element :reply_comment_button
+ end
end
end
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/element.rb b/qa/qa/page/element.rb
index 7a01320901d..9e6fd2fdd4f 100644
--- a/qa/qa/page/element.rb
+++ b/qa/qa/page/element.rb
@@ -28,7 +28,7 @@ module QA
end
def selector_css
- ".#{selector}"
+ %Q([data-qa-selector="#{@name}"],.#{selector})
end
def expression
@@ -40,7 +40,7 @@ module QA
end
def matches?(line)
- !!(line =~ expression)
+ !!(line =~ /["']#{name}['"]|#{expression}/)
end
end
end
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 8970eeb6678..c2b0482d789 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
@@ -58,7 +58,7 @@ module QA
end
end
- Page::Main::Menu.act { has_personal_area? }
+ Page::Main::Menu.perform(&:has_personal_area?)
end
def sign_in_using_admin_credentials
@@ -73,7 +73,7 @@ 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 self.path
@@ -154,7 +154,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 5eb24d2d2ba..d86d554356e 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -5,14 +5,14 @@ module QA
module Main
class Menu < Page::Base
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
- element :user_sign_out_link, 'link_to _("Sign out")' # rubocop:disable QA/ElementWithPattern
- element :settings_link, 'link_to s_("CurrentUser|Settings")' # rubocop:disable QA/ElementWithPattern
+ element :sign_out_link
+ element :settings_link
end
view 'app/views/layouts/header/_default.html.haml' do
element :navbar, required: true
element :user_avatar, required: true
- element :user_menu, '.dropdown-menu' # rubocop:disable QA/ElementWithPattern
+ element :user_menu, required: true
end
view 'app/views/layouts/nav/_dashboard.html.haml' do
@@ -53,7 +53,7 @@ module QA
def sign_out
within_user_menu do
- click_link 'Sign out'
+ click_element :sign_out_link
end
end
@@ -82,7 +82,7 @@ module QA
private
def within_top_menu
- page.within('.qa-navbar') do
+ within_element(:navbar) do
yield
end
end
@@ -91,7 +91,7 @@ module QA
within_top_menu do
click_element :user_avatar
- page.within('.dropdown-menu') do
+ within_element(:user_menu) do
yield
end
end
diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb
index 5f6ddb9a114..2b1a9ab2b6a 100644
--- a/qa/qa/page/main/oauth.rb
+++ b/qa/qa/page/main/oauth.rb
@@ -5,7 +5,7 @@ module QA
module Main
class OAuth < Page::Base
view 'app/views/doorkeeper/authorizations/new.html.haml' do
- element :authorization_button, 'submit_tag _("Authorize")' # rubocop:disable QA/ElementWithPattern
+ element :authorization_button
end
def needs_authorization?
@@ -13,7 +13,7 @@ module QA
end
def authorize!
- click_button 'Authorize'
+ click_element :authorization_button
end
end
end
diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb
index 46a105003d0..c47d2ce9c74 100644
--- a/qa/qa/page/main/sign_up.rb
+++ b/qa/qa/page/main/sign_up.rb
@@ -5,28 +5,28 @@ module QA
module Main
class SignUp < Page::Base
view 'app/views/devise/shared/_signup_box.html.haml' do
- element :new_user_name
- element :new_user_username
- element :new_user_email
- element :new_user_email_confirmation
- element :new_user_password
+ element :new_user_name_field
+ element :new_user_username_field
+ element :new_user_email_field
+ element :new_user_email_confirmation_field
+ element :new_user_password_field
element :new_user_register_button
- element :new_user_accept_terms
+ element :new_user_accept_terms_checkbox
end
def sign_up!(user)
- fill_element :new_user_name, user.name
- fill_element :new_user_username, user.username
- fill_element :new_user_email, user.email
- fill_element :new_user_email_confirmation, user.email
- fill_element :new_user_password, user.password
+ fill_element :new_user_name_field, user.name
+ fill_element :new_user_username_field, user.username
+ fill_element :new_user_email_field, user.email
+ fill_element :new_user_email_confirmation_field, user.email
+ fill_element :new_user_password_field, user.password
- check_element :new_user_accept_terms if has_element?(:new_user_accept_terms)
+ check_element :new_user_accept_terms_checkbox if has_element?(:new_user_accept_terms_checkbox)
signed_in = retry_until do
click_element :new_user_register_button
- Page::Main::Menu.act { has_personal_area? }
+ Page::Main::Menu.perform(&:has_personal_area?)
end
raise "Failed to register and sign in" unless signed_in
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index defd85a5740..0918445d119 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -18,7 +18,7 @@ module QA
element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
element :project_path, 'text_field :path' # rubocop:disable QA/ElementWithPattern
element :project_description, 'text_area :description' # rubocop:disable QA/ElementWithPattern
- element :project_create_button, "submit 'Create project'" # rubocop:disable QA/ElementWithPattern
+ element :project_create_button, "submit _('Create project')" # rubocop:disable QA/ElementWithPattern
element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index 4f625c5f0f0..eb30e0ea02a 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -28,16 +28,12 @@ module QA
end
end
- def await_installed(application_name, button_text: 'Installed')
+ def await_installed(application_name)
within(".js-cluster-application-row-#{application_name}") do
- page.has_text?(button_text, wait: 300)
+ page.has_text?(/Installed|Uninstall/, wait: 300)
end
end
- def await_uninstallable(application_name)
- await_installed(application_name, button_text: 'Uninstall')
- end
-
def ingress_ip
# We need to wait longer since it can take some time before the
# ip address is assigned for the ingress controller
diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb
index c94e1e85256..3c9e8085748 100644
--- a/qa/qa/page/project/sub_menus/common.rb
+++ b/qa/qa/page/project/sub_menus/common.rb
@@ -12,7 +12,11 @@ module QA
end
def within_submenu
- within('.fly-out-list') do
+ if has_css?('.fly-out-list')
+ within('.fly-out-list') do
+ yield
+ end
+ else
yield
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/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb
index 1dd93dd5b88..27ab7b60211 100644
--- a/qa/qa/resource/kubernetes_cluster.rb
+++ b/qa/qa/resource/kubernetes_cluster.rb
@@ -47,7 +47,7 @@ module QA
page.install!(:runner) if @install_runner
page.await_installed(:ingress) if @install_ingress
- page.await_uninstallable(:prometheus) if @install_prometheus
+ page.await_installed(:prometheus) if @install_prometheus
page.await_installed(:runner) if @install_runner
if @install_ingress
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 45cb317e0eb..7969de726e4 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -9,6 +9,7 @@ module QA
:description,
:source_branch,
:target_branch,
+ :target_new_branch,
:assignee,
:milestone,
:labels,
@@ -27,6 +28,7 @@ module QA
Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.branch_name = 'master'
+ resource.new_branch = @target_new_branch
resource.remote_branch = target_branch
end
end
@@ -52,6 +54,7 @@ module QA
@labels = []
@file_name = "added_file.txt"
@file_content = "File Added"
+ @target_new_branch = true
end
def fabricate!
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index ed0779b93cc..2987bb1a213 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -13,6 +13,8 @@ module QA
NotRespondingError = Class.new(RuntimeError)
+ CAPYBARA_MAX_WAIT_TIME = 10
+
def initialize
self.class.configure!
end
@@ -43,6 +45,8 @@ module QA
end
end
+ Capybara.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i
+
return if Capybara.drivers.include?(:chrome)
Capybara.register_driver QA::Runtime::Env.browser do |app|
@@ -119,7 +123,7 @@ module QA
Capybara.configure do |config|
config.default_driver = QA::Runtime::Env.browser
config.javascript_driver = QA::Runtime::Env.browser
- config.default_max_wait_time = 10
+ config.default_max_wait_time = CAPYBARA_MAX_WAIT_TIME
# https://github.com/mattheworiordan/capybara-screenshot/issues/164
config.save_path = ::File.expand_path('../../tmp', __dir__)
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 96f337dc081..e78b9bece19 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'gitlab/qa'
+
module QA
module Runtime
module Env
@@ -7,6 +9,8 @@ module QA
attr_writer :personal_access_token, :ldap_username, :ldap_password
+ ENV_VARIABLES = Gitlab::QA::Runtime::Env::ENV_VARIABLES
+
# The environment variables used to indicate if the environment under test
# supports the given feature
SUPPORTED_FEATURES = {
@@ -173,8 +177,8 @@ module QA
ENV.fetch("GCLOUD_ACCOUNT_EMAIL")
end
- def gcloud_zone
- ENV.fetch('GCLOUD_ZONE')
+ def gcloud_region
+ ENV.fetch('GCLOUD_REGION')
end
def has_gcloud_credentials?
@@ -201,6 +205,10 @@ module QA
enabled?(ENV[SUPPORTED_FEATURES[feature]], default: true)
end
+ def runtime_scenario_attributes
+ ENV['QA_RUNTIME_SCENARIO_ATTRIBUTES']
+ end
+
private
def remote_grid_credentials
diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb
index bd5c4fe5bf5..7f73f1bd01b 100644
--- a/qa/qa/runtime/logger.rb
+++ b/qa/qa/runtime/logger.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'logger'
+require 'forwardable'
module QA
module Runtime
diff --git a/qa/qa/runtime/scenario.rb b/qa/qa/runtime/scenario.rb
index 5067322804b..3662ebe671b 100644
--- a/qa/qa/runtime/scenario.rb
+++ b/qa/qa/runtime/scenario.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'json'
+
module QA
module Runtime
##
@@ -24,6 +26,10 @@ module QA
end
end
+ def from_env(var)
+ JSON.parse(Runtime::Env.runtime_scenario_attributes).each { |k, v| define(k, v) }
+ end
+
def method_missing(name, *)
raise ArgumentError, "Scenario attribute `#{name}` not defined!"
end
diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb
index 40d5c6b1ff1..52f50ec8c27 100644
--- a/qa/qa/scenario/shared_attributes.rb
+++ b/qa/qa/scenario/shared_attributes.rb
@@ -7,6 +7,7 @@ module QA
attribute :gitlab_address, '--address URL', 'Address of the instance to test'
attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests'
+ attribute :parallel, '--parallel', 'Execute tests in parallel'
end
end
end
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index e05269e8d55..b4d70fc191a 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -14,7 +14,7 @@ module QA
Page::Validator.new(pages)
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 16a89591eb2..40263e94065 100644
--- a/qa/qa/service/kubernetes_cluster.rb
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -28,17 +28,17 @@ module QA
create #{cluster_name}
#{auth_options}
--enable-basic-auth
- --zone #{Runtime::Env.gcloud_zone}
+ --region #{Runtime::Env.gcloud_region}
&& gcloud container clusters
get-credentials
- --zone #{Runtime::Env.gcloud_zone}
+ --region #{Runtime::Env.gcloud_region}
#{cluster_name}
CMD
@api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'`
@admin_user = "#{cluster_name}-admin"
- master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --zone #{Runtime::Env.gcloud_zone} --format 'json(masterAuth.username, masterAuth.password)'`)
+ master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --region #{Runtime::Env.gcloud_region} --format 'json(masterAuth.username, masterAuth.password)'`)
shell <<~CMD.tr("\n", ' ')
kubectl config set-credentials #{@admin_user}
--username #{master_auth['masterAuth']['username']}
@@ -66,10 +66,10 @@ module QA
def remove!
shell <<~CMD.tr("\n", ' ')
gcloud container clusters delete
- --zone #{Runtime::Env.gcloud_zone}
- #{cluster_name}
- --quiet --async
- CMD
+ --region #{Runtime::Env.gcloud_region}
+ #{cluster_name}
+ --quiet --async
+ CMD
end
private
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
index a9de64e357a..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,17 +5,17 @@ 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! do |group|
+ source_group = Resource::Group.fabricate_via_api! do |group|
group.path = 'source-group'
end
- target_group = Resource::Group.fabricate! do |group|
+ target_group = Resource::Group.fabricate_via_api! do |group|
group.path = 'target-group'
end
- project = Resource::Project.fabricate! do |project|
+ project = Resource::Project.fabricate_via_api! do |project|
project.group = source_group
project.name = 'transfer-project'
project.initialize_with_readme = true
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
index cf225a639b6..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,14 +5,16 @@ 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
end
- Page::Main::Menu.perform do |menu|
- menu.sign_out
+ Support::Retrier.retry_until(sleep_interval: 0.5) do
+ Page::Main::Menu.perform(&:sign_out)
+
+ Page::Main::Login.perform(&:has_sign_in_tab?)
end
Page::Main::Login.perform do |form|
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/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/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index d1747280227..f51c16f472c 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -9,7 +9,7 @@ module QA
user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
- project = Resource::Project.fabricate! do |resource|
+ project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'add-member-project'
end
project.visit!
diff --git a/qa/qa/specs/features/browser_ui/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
new file mode 100644
index 00000000000..0b1bd00ac8d
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Plan' do
+ describe 'Issue comments' 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'
+ end
+ issue.visit!
+ end
+
+ 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'
+
+ show.comment(first_version_of_comment)
+
+ expect(show).to have_content(first_version_of_comment)
+
+ show.edit_comment(second_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
+ 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/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
index 2b3d9b1711d..36cbd1b81f0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
@@ -56,7 +56,7 @@ module QA
project.visit!
end
- it 'branches are correctly listed after CRUD operations' do
+ it 'lists branches correctly after CRUD operations' do
Page::Project::Menu.perform(&:go_to_repository_branches)
expect(page).to have_content(master_branch)
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_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/4_verify/ci_variable/add_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
index b060f15168c..b2c70547421 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
@@ -7,12 +7,12 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- project = Resource::Project.fabricate! do |project|
+ project = Resource::Project.fabricate_via_api! do |project|
project.name = 'project-with-ci-variables'
project.description = 'project with CI variables'
end
- Resource::CiVariable.fabricate! do |resource|
+ Resource::CiVariable.fabricate_via_api! do |resource|
resource.project = project
resource.key = 'VARIABLE_KEY'
resource.value = 'some_CI_variable'
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/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
index aa01e5a618e..6f39a755392 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/26
- context 'Release', :quarantine do
+ context 'Release' do
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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 caa9be341b4..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,12 +5,12 @@ 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
- deploy_token = Resource::DeployToken.fabricate! do |resource|
+ deploy_token = Resource::DeployToken.fabricate_via_browser_ui! do |resource|
resource.name = deploy_token_name
resource.expires_at = one_week_from_now
end
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 99f0838b864..60c1e105ae6 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -55,8 +55,7 @@ module QA
end
end
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/108
- describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do
+ describe 'Auto DevOps support', :orchestrated, :kubernetes do
context 'when rbac is enabled' do
before(:all) do
@cluster = Service::KubernetesCluster.new.create!
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
index 8383dcdb983..94d20106de4 100644
--- 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
@@ -5,8 +5,8 @@ module QA
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::Main::Login.perform(&:sign_in_using_credentials)
+ Page::Main::Menu.perform(&:go_to_groups)
Page::Dashboard::Groups.perform do |page|
page.click_new_group
diff --git a/qa/qa/specs/parallel_runner.rb b/qa/qa/specs/parallel_runner.rb
new file mode 100644
index 00000000000..b92fdb610b6
--- /dev/null
+++ b/qa/qa/specs/parallel_runner.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'open3'
+
+module QA
+ module Specs
+ module ParallelRunner
+ module_function
+
+ def run(args)
+ unless args.include?('--')
+ index = args.index { |opt| opt.include?('features') }
+
+ args.insert(index, '--') if index
+ end
+
+ env = {}
+ Runtime::Env::ENV_VARIABLES.each_key do |key|
+ env[key] = ENV[key] if ENV[key]
+ end
+ env['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = Runtime::Scenario.attributes.to_json
+ env['GITLAB_QA_ACCESS_TOKEN'] = Runtime::API::Client.new(:gitlab).personal_access_token unless env['GITLAB_QA_ACCESS_TOKEN']
+
+ cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{args.flatten.join(' ')}"
+ ::Open3.popen2e(env, cmd) do |_, out, wait|
+ out.each { |line| puts line }
+
+ exit wait.value.exitstatus
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index f1cb9378de8..6aa08cf77b4 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
+require 'knapsack'
require 'rspec/core'
require 'rspec/expectations'
-require 'knapsack'
module QA
module Specs
@@ -17,44 +17,56 @@ module QA
@options = []
end
- def perform
- args = []
- args.push('--tty') if tty
+ def paths_from_knapsack
+ allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
+
+ QA::Runtime::Logger.info ''
+ QA::Runtime::Logger.info 'Report specs:'
+ QA::Runtime::Logger.info allocator.report_node_tests.join(', ')
+ QA::Runtime::Logger.info ''
+ QA::Runtime::Logger.info 'Leftover specs:'
+ QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ')
+ QA::Runtime::Logger.info ''
+
+ ['--', allocator.node_tests]
+ end
+
+ def rspec_tags
+ tags_for_rspec = []
if tags.any?
- tags.each { |tag| args.push(['--tag', tag.to_s]) }
+ tags.each { |tag| tags_for_rspec.push(['--tag', tag.to_s]) }
else
- args.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any?
+ tags_for_rspec.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any?
end
- args.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
+ tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
QA::Runtime::Env.supported_features.each_key do |key|
- args.push(["--tag", "~requires_#{key}"]) unless QA::Runtime::Env.can_test? key
+ tags_for_rspec.push(%W[--tag ~requires_#{key}]) unless QA::Runtime::Env.can_test? key
end
- args.push(options)
+ tags_for_rspec
+ end
- Runtime::Browser.configure!
+ def perform
+ args = []
+ args.push('--tty') if tty
+ args.push(rspec_tags)
+ args.push(options)
if Runtime::Env.knapsack?
- allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
-
- QA::Runtime::Logger.info ''
- QA::Runtime::Logger.info 'Report specs:'
- QA::Runtime::Logger.info allocator.report_node_tests.join(', ')
- QA::Runtime::Logger.info ''
- QA::Runtime::Logger.info 'Leftover specs:'
- QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ')
- QA::Runtime::Logger.info ''
-
- args.push(['--', allocator.node_tests])
+ args.push(paths_from_knapsack)
else
args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} }
end
- RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
- abort if status.nonzero?
+ if Runtime::Scenario.attributes[:parallel]
+ ParallelRunner.run(args.flatten)
+ else
+ RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
+ abort if status.nonzero?
+ end
end
end
end
diff --git a/qa/qa/support/retrier.rb b/qa/qa/support/retrier.rb
index 8be4e9f5365..720f1d17037 100644
--- a/qa/qa/support/retrier.rb
+++ b/qa/qa/support/retrier.rb
@@ -23,6 +23,25 @@ module QA
raise
end
end
+
+ def retry_until(max_attempts: 3, reload_page: nil, sleep_interval: 0)
+ QA::Runtime::Logger.debug("with retry_until: max_attempts #{max_attempts}; sleep_interval #{sleep_interval}; reload_page:#{reload_page}")
+ attempts = 0
+
+ while attempts < max_attempts
+ QA::Runtime::Logger.debug("Attempt number #{attempts + 1}")
+ result = yield
+ return result if result
+
+ sleep sleep_interval
+
+ reload_page.refresh if reload_page
+
+ attempts += 1
+ end
+
+ false
+ end
end
end
end
diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb
index b0477951967..26bcb2fe958 100644
--- a/qa/qa/tools/generate_perf_testdata.rb
+++ b/qa/qa/tools/generate_perf_testdata.rb
@@ -59,8 +59,8 @@ module QA
group_search_response = create_a_group_api_req(@group_name, @visibility)
group = JSON.parse(group_search_response.body)
@urls[:group_page] = group["web_url"]
- group["id"]
STDOUT.puts "Created a group: #{@urls[:group_page]}"
+ group["id"]
end
def create_project(group_id)
@@ -196,6 +196,7 @@ module QA
project_path = "#{@group_name}%2F#{@project_name}"
branch_name = "branch_with_many_commits-#{SecureRandom.hex(8)}"
file_name = "file_for_many_commits.txt"
+
create_a_branch_api_req(branch_name, project_path)
create_a_new_file_api_req(file_name, branch_name, project_path, "Initial commit for new file", "Initial file content")
create_mr_response = create_a_merge_request_api_req(project_path, branch_name, "master", "MR with many commits-#{SecureRandom.hex(8)}")
@@ -203,7 +204,7 @@ module QA
100.times do |i|
update_file_api_req(file_name, branch_name, project_path, Faker::Lorem.sentences(5).join(" "), Faker::Lorem.sentences(500).join("\n"))
end
- STDOUT.puts "Created an MR with many commits: #{@urls[:mr_with_many_commits]}"
+ STDOUT.puts "Using branch: #{branch_name}, created an MR with many commits: #{@urls[:mr_with_many_commits]}"
end
private
@@ -211,56 +212,88 @@ module QA
# API Requests
def create_a_discussion_on_issue_api_req(project_path_or_id, issue_id, body)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/discussions").url, "body=\"#{body}\""
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/discussions").url, "body=\"#{body}\""
+ end
end
def update_a_discussion_on_issue_api_req(project_path_or_id, mr_iid, discussion_id, resolved_status)
- put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions/#{discussion_id}").url, "resolved=#{resolved_status}"
+ call_api(expected_response_code: 200) do
+ put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions/#{discussion_id}").url, "resolved=#{resolved_status}"
+ end
end
def create_a_discussion_on_mr_api_req(project_path_or_id, mr_iid, body)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions").url,
- "body=\"#{body}\""
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions").url, "body=\"#{body}\""
+ end
end
def create_a_label_api_req(project_path_or_id, name, color)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/labels").url, "name=#{name}&color=#{color}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/labels").url, "name=#{name}&color=#{color}"
+ end
end
def create_a_todo_api_req(project_path_or_id, issue_id)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/todo").url, nil
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/todo").url, nil
+ end
end
def create_an_issue_api_req(project_path_or_id, title, description)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues").url, "title=#{title}&description=#{description}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues").url, "title=#{title}&description=#{description}"
+ end
end
def update_an_issue_api_req(project_path_or_id, issue_id, description, labels_list)
- put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}").url, "description=#{description}&labels=#{labels_list}"
+ call_api(expected_response_code: 200) do
+ put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}").url, "description=#{description}&labels=#{labels_list}"
+ end
end
def create_a_project_api_req(project_name, group_id, visibility)
- post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{project_name}&namespace_id=#{group_id}&visibility=#{visibility}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{project_name}&namespace_id=#{group_id}&visibility=#{visibility}"
+ end
end
def create_a_group_api_req(group_name, visibility)
- post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{group_name}&path=#{group_name}&visibility=#{visibility}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{group_name}&path=#{group_name}&visibility=#{visibility}"
+ end
end
def create_a_branch_api_req(branch_name, project_path_or_id)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/branches").url, "branch=#{branch_name}&ref=master"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/branches").url, "branch=#{branch_name}&ref=master"
+ end
end
def create_a_new_file_api_req(file_path, branch_name, project_path_or_id, commit_message, content)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\""
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\""
+ end
end
def create_a_merge_request_api_req(project_path_or_id, source_branch, target_branch, mr_title)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests").url, "source_branch=#{source_branch}&target_branch=#{target_branch}&title=#{mr_title}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests").url, "source_branch=#{source_branch}&target_branch=#{target_branch}&title=#{mr_title}"
+ end
end
def update_file_api_req(file_path, branch_name, project_path_or_id, commit_message, content)
- put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\""
+ call_api(expected_response_code: 200) do
+ put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\""
+ end
+ end
+
+ def call_api(expected_response_code: 200)
+ response = yield
+ raise "API call failed with response code: #{response.code} and body: #{response.body}" unless response.code == expected_response_code
+
+ response
end
end
end
diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb
index f746fe06e40..20d4a00c020 100644
--- a/qa/spec/page/element_spec.rb
+++ b/qa/spec/page/element_spec.rb
@@ -11,7 +11,7 @@ describe QA::Page::Element do
describe '#selector_css' do
it 'transforms element name into QA-specific clickable css selector' do
expect(described_class.new(:sign_in_button).selector_css)
- .to eq '.qa-sign-in-button'
+ .to include('.qa-sign-in-button')
end
end
@@ -49,6 +49,10 @@ describe QA::Page::Element do
it 'does not match if QA selector is not there' do
expect(subject.matches?('some_name selector')).to be false
end
+
+ it 'matches when element name is specified' do
+ expect(subject.matches?('data:{qa:{selector:"some_name"}}')).to be true
+ end
end
describe 'attributes' do
@@ -106,4 +110,11 @@ describe QA::Page::Element do
end
end
end
+
+ describe 'data-qa selectors' do
+ subject { described_class.new(:my_element) }
+ it 'properly translates to a data-qa-selector' do
+ expect(subject.selector_css).to include(%q([data-qa-selector="my_element"]))
+ end
+ end
end
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index 0f1ed039149..92a4f7b40e6 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -91,26 +91,26 @@ describe QA::Support::Page::Logging do
it 'logs has_element?' do
expect { subject.has_element?(:element) }
- .to output(/has_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process
+ .to output(/has_element\? :element \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process
end
it 'logs has_element? with text' do
expect { subject.has_element?(:element, text: "some text") }
- .to output(/has_element\? :element with text \"some text\" \(wait: 2\) returned: true/).to_stdout_from_any_process
+ .to output(/has_element\? :element with text \"some text\" \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process
end
it 'logs has_no_element?' do
allow(page).to receive(:has_no_css?).and_return(true)
expect { subject.has_no_element?(:element) }
- .to output(/has_no_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process
+ .to output(/has_no_element\? :element \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process
end
it 'logs has_no_element? with text' do
allow(page).to receive(:has_no_css?).and_return(true)
expect { subject.has_no_element?(:element, text: "more text") }
- .to output(/has_no_element\? :element with text \"more text\" \(wait: 2\) returned: true/).to_stdout_from_any_process
+ .to output(/has_no_element\? :element with text \"more text\" \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process
end
it 'logs has_text?' do
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index f25dbf3a8ab..21bfd2876a9 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -8,6 +8,10 @@ if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK']
Knapsack::Adapters::RSpecAdapter.bind
end
+QA::Runtime::Browser.configure!
+
+QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes
+
%w[helpers shared_examples].each do |d|
Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f }
end
diff --git a/qa/spec/specs/parallel_runner_spec.rb b/qa/spec/specs/parallel_runner_spec.rb
new file mode 100644
index 00000000000..67d94a1f648
--- /dev/null
+++ b/qa/spec/specs/parallel_runner_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+describe QA::Specs::ParallelRunner do
+ include Helpers::StubENV
+
+ before do
+ allow(QA::Runtime::Scenario).to receive(:attributes).and_return(parallel: true)
+ stub_env('GITLAB_QA_ACCESS_TOKEN', 'skip_token_creation')
+ end
+
+ it 'passes args to parallel_tests' do
+ expect_cli_arguments(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS])
+
+ subject.run(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS])
+ end
+
+ it 'passes a given test path to parallel_tests and adds a separator' do
+ expect_cli_arguments(%w[-- qa/specs/features/foo])
+
+ subject.run(%w[qa/specs/features/foo])
+ end
+
+ it 'passes tags and test paths to parallel_tests and adds a separator' do
+ expect_cli_arguments(%w[--tag smoke -- qa/specs/features/foo qa/specs/features/bar])
+
+ subject.run(%w[--tag smoke qa/specs/features/foo qa/specs/features/bar])
+ end
+
+ it 'passes tags and test paths with separators to parallel_tests' do
+ expect_cli_arguments(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar])
+
+ subject.run(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar])
+ end
+
+ it 'passes supported environment variables' do
+ # Test only env vars starting with GITLAB because some of the others
+ # affect how the runner behaves, and we're not concerned with those
+ # behaviors in this test
+ gitlab_env_vars = QA::Runtime::Env::ENV_VARIABLES.reject { |v| !v.start_with?('GITLAB') }
+
+ gitlab_env_vars.each do |k, v|
+ stub_env(k, v)
+ end
+
+ gitlab_env_vars['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = '{"parallel":true}'
+
+ expect_cli_arguments([], gitlab_env_vars)
+
+ subject.run([])
+ end
+
+ def expect_cli_arguments(arguments, env = { 'QA_RUNTIME_SCENARIO_ATTRIBUTES' => '{"parallel":true}' })
+ cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{arguments.join(' ')}"
+ expect(Open3).to receive(:popen2e)
+ .with(hash_including(env), cmd)
+ .and_return(0)
+ end
+end
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index f94145d148e..3d98f03a982 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -58,11 +58,11 @@ describe QA::Specs::Runner do
end
end
- context 'when "-- qa/specs/features/foo" is set as options' do
- subject { described_class.new.tap { |runner| runner.options = %w[-- qa/specs/features/foo] } }
+ context 'when "--tag smoke" and "qa/specs/features/foo" are set as options' do
+ subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke qa/specs/features/foo] } }
- it 'passes the given tests path and excludes the orchestrated tag' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--', 'qa/specs/features/foo'])
+ it 'focuses on the given tag and includes the path without excluding the orchestrated tag' do
+ expect_rspec_runner_arguments(['--tag', 'smoke', 'qa/specs/features/foo'])
subject.perform
end
@@ -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/gitlab/rails_logger.rb b/rubocop/cop/gitlab/rails_logger.rb
new file mode 100644
index 00000000000..d1a06a9a100
--- /dev/null
+++ b/rubocop/cop/gitlab/rails_logger.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require_relative '../../code_reuse_helpers'
+
+module RuboCop
+ module Cop
+ module Gitlab
+ class RailsLogger < ::RuboCop::Cop::Cop
+ include CodeReuseHelpers
+
+ # This cop checks for the Rails.logger in the codebase
+ #
+ # @example
+ #
+ # # bad
+ # Rails.logger.error("Project #{project.full_path} could not be saved")
+ #
+ # # good
+ # Gitlab::AppLogger.error("Project %{project_path} could not be saved" % { project_path: project.full_path })
+ MSG = 'Use a structured JSON logger instead of `Rails.logger`. ' \
+ 'https://docs.gitlab.com/ee/development/logging.html'.freeze
+
+ def_node_matcher :rails_logger?, <<~PATTERN
+ (send (const nil? :Rails) :logger ... )
+ PATTERN
+
+ WHITELISTED_DIRECTORIES = %w[
+ spec
+ ].freeze
+
+ def on_send(node)
+ return if in_whitelisted_directory?(node)
+ return unless rails_logger?(node)
+
+ add_offense(node, location: :expression)
+ end
+
+ def in_whitelisted_directory?(node)
+ path = file_path_for_node(node)
+
+ WHITELISTED_DIRECTORIES.any? do |directory|
+ path.start_with?(
+ File.join(rails_root, directory),
+ File.join(rails_root, 'ee', directory)
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/qa/element_with_pattern.rb b/rubocop/cop/qa/element_with_pattern.rb
index d14eeaaeaf3..d48f4725488 100644
--- a/rubocop/cop/qa/element_with_pattern.rb
+++ b/rubocop/cop/qa/element_with_pattern.rb
@@ -30,7 +30,7 @@ module RuboCop
return if args.first.nil?
args.first.each_node(:str) do |arg|
- add_offense(arg, message: MESSAGE % "qa-#{element_name.value.to_s.tr('_', '-')}")
+ add_offense(arg, message: MESSAGE % "data-qa-selector=#{element_name.value}")
end
end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 27c63d92ae5..ba61a634d97 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -3,6 +3,7 @@ require_relative 'cop/gitlab/predicate_memoization'
require_relative 'cop/gitlab/httparty'
require_relative 'cop/gitlab/finder_with_find_by'
require_relative 'cop/gitlab/union'
+require_relative 'cop/gitlab/rails_logger'
require_relative 'cop/include_action_view_context'
require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/safe_params'
diff --git a/scripts/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/gather-test-memory-data b/scripts/gather-test-memory-data
new file mode 100755
index 00000000000..9992a83e6a6
--- /dev/null
+++ b/scripts/gather-test-memory-data
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+
+require 'csv'
+
+def join_csv_files(output_path, input_paths)
+ return if input_paths.empty?
+
+ input_csvs = input_paths.map do |input_path|
+ CSV.read(input_path, headers: true)
+ end
+
+ CSV.open(output_path, "w", headers: input_csvs.first.headers, write_headers: true) do |output_csv|
+ input_csvs.each do |input_csv|
+ input_csv.each do |line|
+ output_csv << line
+ end
+ end
+ end
+end
+
+join_csv_files('tmp/memory_test/report.csv', Dir['tmp/memory_test/*.csv'].sort)
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-doc.sh b/scripts/lint-doc.sh
index 2055ce7f09d..8c9b8b9fb02 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -45,7 +45,7 @@ then
then
echo
echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2
- echo ' https://docs.gitlab.com/ee/development/writing_documentation.html#location-and-naming-documents'
+ echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#working-with-directories-and-files'
echo
exit 1
fi
@@ -55,7 +55,7 @@ then
then
echo
echo ' ✖ ERROR: New README.md file(s) detected, prefer index.md over README.md.' >&2
- echo ' https://docs.gitlab.com/ee/development/writing_documentation.html#location-and-naming-documents'
+ echo ' https://docs.gitlab.com/ee/development/documentation/styleguide.html#working-with-directories-and-files'
echo
exit 1
fi
diff --git a/scripts/lint-rugged b/scripts/lint-rugged
index 9466c62a415..f40f0489c1a 100755
--- a/scripts/lint-rugged
+++ b/scripts/lint-rugged
@@ -10,7 +10,14 @@ 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/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 2bf654b1e24..363ab2c6540 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}'"
@@ -132,13 +132,14 @@ function install_external_dns() {
echoinfo "Installing external-dns Helm chart"
helm repo update
# Default requested: CPU => 0, memory => 0
- helm install stable/external-dns \
+ helm install stable/external-dns --version '^2.2.1' \
-n "${release_name}" \
--namespace "${KUBE_NAMESPACE}" \
--set provider="aws" \
- --set aws.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \
- --set aws.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \
+ --set aws.credentials.secretKey="${REVIEW_APPS_AWS_SECRET_KEY}" \
+ --set aws.credentials.accessKey="${REVIEW_APPS_AWS_ACCESS_KEY}" \
--set aws.zoneType="public" \
+ --set aws.batchChangeSize=400 \
--set domainFilters[0]="${domain}" \
--set txtOwnerId="${KUBE_NAMESPACE}" \
--set rbac.create="true" \
diff --git a/scripts/trigger-build-docs b/scripts/trigger-build-docs
index 0fc8f6fbd4b..83841512f1c 100755
--- a/scripts/trigger-build-docs
+++ b/scripts/trigger-build-docs
@@ -13,7 +13,7 @@ end
#
# The remote docs project
#
-GITLAB_DOCS_REPO = 'gitlab-com/gitlab-docs'.freeze
+GITLAB_DOCS_REPO = 'gitlab-org/gitlab-docs'.freeze
#
# Truncate the remote docs branch name otherwise we hit the filesystem
@@ -31,7 +31,7 @@ end
# to avoid race conditions, since a triggered pipeline will also run right
# after the branch creation. This only happens the very first time a branch
# is created and will be skipped in subsequent runs. Read more in
-# https://gitlab.com/gitlab-com/gitlab-docs/issues/154.
+# https://gitlab.com/gitlab-org/gitlab-docs/issues/154.
#
def create_remote_branch
Gitlab.create_branch(GITLAB_DOCS_REPO, docs_branch, 'master')
@@ -81,7 +81,7 @@ def slug
end
#
-# Overriding vars in https://gitlab.com/gitlab-com/gitlab-docs/blob/master/.gitlab-ci.yml
+# Overriding vars in https://gitlab.com/gitlab-org/gitlab-docs/blob/master/.gitlab-ci.yml
#
def param_name
"BRANCH_#{slug.upcase}"
diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb
index 5ad5f9cdeea..4eb0545eb6c 100644
--- a/spec/controllers/admin/application_settings_controller_spec.rb
+++ b/spec/controllers/admin/application_settings_controller_spec.rb
@@ -41,7 +41,7 @@ describe Admin::ApplicationSettingsController do
it 'returns JSON data' do
get :usage_data, format: :json
- body = JSON.parse(response.body)
+ body = json_response
expect(body["version"]).to eq(Gitlab::VERSION)
expect(body).to include('counts')
expect(response.status).to eq(200)
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/runners_controller_spec.rb b/spec/controllers/admin/runners_controller_spec.rb
index 78c5e2a2656..bbeda7dae0f 100644
--- a/spec/controllers/admin/runners_controller_spec.rb
+++ b/spec/controllers/admin/runners_controller_spec.rb
@@ -23,10 +23,11 @@ describe Admin::RunnersController do
control_count = ActiveRecord::QueryRecorder.new { get :index }.count
- create(:ci_runner, :tagged_only)
+ create_list(:ci_runner, 5, :tagged_only)
# There is still an N+1 query for `runner.builds.count`
- expect { get :index }.not_to exceed_query_limit(control_count + 1)
+ # We also need to add 1 because it takes 2 queries to preload tags
+ expect { get :index }.not_to exceed_query_limit(control_count + 6)
expect(response).to have_gitlab_http_status(200)
expect(response.body).to have_content('tag1')
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 447a12b2fac..84bbbac39b0 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -63,8 +63,6 @@ describe ApplicationController do
sign_in user
end
- let(:json_response) { JSON.parse(response.body) }
-
controller(described_class) do
def index
render json: Gon.all_variables
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 3f1c0ae8ac4..eaa5d6cd073 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -295,28 +295,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 c84bb913cad..0db58fbefc1 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -52,10 +52,8 @@ describe Boards::IssuesController do
list_issues user: user, board: board, list: list2
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('entities/issue_boards')
- expect(parsed_response['issues'].length).to eq 2
+ expect(json_response['issues'].length).to eq 2
expect(development.issues.map(&:relative_position)).not_to include(nil)
end
@@ -123,10 +121,8 @@ describe Boards::IssuesController do
list_issues user: user, board: board
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('entities/issue_boards')
- expect(parsed_response['issues'].length).to eq 2
+ expect(json_response['issues'].length).to eq 2
end
end
@@ -164,6 +160,215 @@ describe Boards::IssuesController do
end
end
+ describe 'PUT bulk_move' do
+ let(:todo) { create(:group_label, group: group, name: 'Todo') }
+ let(:development) { create(:group_label, group: group, name: 'Development') }
+ let(:user) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
+ let(:guest) { create(:group_member, :guest, user: create(:user), group: group ).user }
+ let(:project) { create(:project, group: group) }
+ let(:group) { create(:group) }
+ let(:board) { create(:board, project: project) }
+ let(:list1) { create(:list, board: board, label: todo, position: 0) }
+ let(:list2) { create(:list, board: board, label: development, position: 1) }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [todo], author: user, relative_position: 10) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [todo], author: user, relative_position: 20) }
+ let(:issue3) { create(:labeled_issue, project: project, labels: [todo], author: user, relative_position: 30) }
+ let(:issue4) { create(:labeled_issue, project: project, labels: [development], author: user, relative_position: 100) }
+
+ let(:move_params) do
+ {
+ board_id: board.id,
+ ids: [issue1.id, issue2.id, issue3.id],
+ from_list_id: list1.id,
+ to_list_id: list2.id,
+ move_before_id: issue4.id,
+ move_after_id: nil
+ }
+ end
+
+ before do
+ project.add_maintainer(user)
+ project.add_guest(guest)
+ end
+
+ shared_examples 'move issues endpoint provider' do
+ before do
+ sign_in(signed_in_user)
+ end
+
+ it 'responds as expected' do
+ put :bulk_move, params: move_issues_params
+ expect(response).to have_gitlab_http_status(expected_status)
+
+ if expected_status == 200
+ expect(json_response).to include(
+ 'count' => move_issues_params[:ids].size,
+ 'success' => true
+ )
+
+ expect(json_response['issues'].pluck('id')).to match_array(move_issues_params[:ids])
+ end
+ end
+
+ it 'moves issues as expected' do
+ put :bulk_move, params: move_issues_params
+ expect(response).to have_gitlab_http_status(expected_status)
+
+ list_issues user: requesting_user, board: board, list: list2
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(response).to match_response_schema('entities/issue_boards')
+
+ responded_issues = json_response['issues']
+ expect(responded_issues.length).to eq expected_issue_count
+
+ ids_in_order = responded_issues.pluck('id')
+ expect(ids_in_order).to eq(expected_issue_ids_in_order)
+ end
+ end
+
+ context 'when items are moved to another list' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) { move_params }
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 4 }
+ let(:expected_issue_ids_in_order) { [issue4.id, issue1.id, issue2.id, issue3.id] }
+ end
+ end
+
+ context 'when moving just one issue' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:ids] = [issue2.id]
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 2 }
+ let(:expected_issue_ids_in_order) { [issue4.id, issue2.id] }
+ end
+ end
+
+ context 'when user is not allowed to move issue' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { guest }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:ids] = [issue2.id]
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 403 }
+ let(:expected_issue_count) { 1 }
+ let(:expected_issue_ids_in_order) { [issue4.id] }
+ end
+ end
+
+ context 'when issues should be moved visually above existing issue in list' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:move_after_id] = issue4.id
+ hash[:move_before_id] = nil
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 4 }
+ let(:expected_issue_ids_in_order) { [issue1.id, issue2.id, issue3.id, issue4.id] }
+ end
+ end
+
+ context 'when destination list is empty' do
+ before do
+ # Remove issue from list
+ issue4.labels -= [development]
+ issue4.save!
+ end
+
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:move_before_id] = nil
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 3 }
+ let(:expected_issue_ids_in_order) { [issue1.id, issue2.id, issue3.id] }
+ end
+ end
+
+ context 'when no position arguments are given' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:move_before_id] = nil
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 4 }
+ let(:expected_issue_ids_in_order) { [issue1.id, issue2.id, issue3.id, issue4.id] }
+ end
+ end
+
+ context 'when move_before_id and move_after_id are given' do
+ let(:issue5) { create(:labeled_issue, project: project, labels: [development], author: user, relative_position: 90) }
+
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:move_before_id] = issue5.id
+ hash[:move_after_id] = issue4.id
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 200 }
+ let(:expected_issue_count) { 5 }
+ let(:expected_issue_ids_in_order) { [issue5.id, issue1.id, issue2.id, issue3.id, issue4.id] }
+ end
+ end
+
+ context 'when request contains too many issues' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:ids] = (0..51).to_a
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 422 }
+ let(:expected_issue_count) { 1 }
+ let(:expected_issue_ids_in_order) { [issue4.id] }
+ end
+ end
+
+ context 'when request is malformed' do
+ it_behaves_like 'move issues endpoint provider' do
+ let(:signed_in_user) { user }
+ let(:move_issues_params) do
+ move_params.dup.tap do |hash|
+ hash[:ids] = 'foobar'
+ end
+ end
+ let(:requesting_user) { user }
+ let(:expected_status) { 400 }
+ let(:expected_issue_count) { 1 }
+ let(:expected_issue_ids_in_order) { [issue4.id] }
+ end
+ end
+ end
+
def list_issues(user:, board:, list: nil)
sign_in(user)
diff --git a/spec/controllers/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index e1f75fa3395..418ca6f3210 100644
--- a/spec/controllers/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -26,10 +26,8 @@ describe Boards::ListsController do
read_board_list user: user, board: board
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('lists')
- expect(parsed_response.length).to eq 3
+ expect(json_response.length).to eq 3
end
context 'with unauthorized user' do
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/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/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index 5e0f64ccca4..e4232c2c1ab 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -63,10 +63,8 @@ describe Groups::BoardsController do
list_boards format: :json
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('boards')
- expect(parsed_response.length).to eq 1
+ expect(json_response.length).to eq 1
end
context 'with unauthorized user' do
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index 19b18091aef..bf164aeed38 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -73,7 +73,7 @@ describe Groups::MilestonesController do
it 'lists legacy group milestones and group milestones' do
get :index, params: { group_id: group.to_param }, format: :json
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones.count).to eq(2)
expect(milestones.first["title"]).to eq("group milestone")
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 19d739fcf4f..92f005faf4a 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
describe HealthCheckController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
let(:xml_response) { Hash.from_xml(response.body)['hash'] }
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
let(:whitelisted_ip) { '127.0.0.1' }
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index fc62a8310aa..e82dcfcdb64 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
describe HealthController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
let(:token) { Gitlab::CurrentSettings.health_check_access_token }
let(:whitelisted_ip) { '127.0.0.1' }
let(:not_whitelisted_ip) { '127.0.0.2' }
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/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
index ee454a7818c..84027119491 100644
--- a/spec/controllers/metrics_controller_spec.rb
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
describe MetricsController do
include StubENV
- let(:json_response) { JSON.parse(response.body) }
let(:metrics_multiproc_dir) { Dir.mktmpdir }
let(:whitelisted_ip) { '127.0.0.1' }
let(:whitelisted_ip_range) { '10.0.0.0/24' }
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index 44500d3cde3..45aebd1554c 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -160,7 +160,7 @@ describe Projects::BlobController do
it 'renders diff context lines Gitlab::Diff::Line array' do
do_get(since: 1, to: 2, offset: 0, from_merge_request: true)
- lines = JSON.parse(response.body)
+ lines = json_response
expect(lines.size).to eq(diff_lines.size)
lines.each do |line|
@@ -173,7 +173,7 @@ describe Projects::BlobController do
it 'handles full being true' do
do_get(full: true, from_merge_request: true)
- lines = JSON.parse(response.body)
+ lines = json_response
expect(lines.size).to eq(diff_lines.size)
end
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index c07afc57aea..543479d8dd5 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -69,10 +69,8 @@ describe Projects::BoardsController do
list_boards format: :json
- parsed_response = JSON.parse(response.body)
-
expect(response).to match_response_schema('boards')
- expect(parsed_response.length).to eq 2
+ expect(json_response.length).to eq 2
end
context 'with unauthorized user' do
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index b30966e70a7..f5bcea4a097 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -495,10 +495,8 @@ describe Projects::BranchesController do
search: 'master'
}
- parsed_response = JSON.parse(response.body)
-
- expect(parsed_response.length).to eq 1
- expect(parsed_response.first).to eq 'master'
+ expect(json_response.length).to eq 1
+ expect(json_response.first).to eq 'master'
end
end
@@ -591,8 +589,7 @@ describe Projects::BranchesController do
end
it 'returns the commit counts behind and ahead of default branch' do
- parsed_response = JSON.parse(response.body)
- expect(parsed_response).to eq(
+ expect(json_response).to eq(
"fix" => { "behind" => 29, "ahead" => 2 },
"branch-merged" => { "behind" => 1, "ahead" => 0 },
"add-pdf-file" => { "behind" => 0, "ahead" => 3 }
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index b5c6382a26d..58a1d96d010 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -378,8 +378,8 @@ describe Projects::CommitController do
get_pipelines(id: commit.id, format: :json)
expect(response).to be_ok
- expect(JSON.parse(response.body)['pipelines']).not_to be_empty
- expect(JSON.parse(response.body)['count']['all']).to eq 1
+ expect(json_response['pipelines']).not_to be_empty
+ expect(json_response['count']['all']).to eq 1
expect(response).to include_pagination_headers
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 92380a2bf09..48a92a772dc 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -302,8 +302,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- signatures = parsed_body['signatures']
+ signatures = json_response['signatures']
expect(signatures.size).to eq(1)
expect(signatures.first['commit_sha']).to eq(signature_commit.sha)
@@ -332,8 +331,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['signatures']).to be_empty
+ expect(json_response['signatures']).to be_empty
end
end
@@ -345,8 +343,7 @@ describe Projects::CompareController do
signatures_request
expect(response).to have_gitlab_http_status(200)
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['signatures']).to be_empty
+ expect(json_response['signatures']).to be_empty
end
end
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..8fc3ae0aa32
--- /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_success
+ 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_success
+ 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_success
+
+ 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/deploy_keys_controller_spec.rb b/spec/controllers/projects/deploy_keys_controller_spec.rb
index fcd14f13863..ccad76eaddd 100644
--- a/spec/controllers/projects/deploy_keys_controller_spec.rb
+++ b/spec/controllers/projects/deploy_keys_controller_spec.rb
@@ -52,12 +52,10 @@ describe Projects::DeployKeysController do
it 'returns json in a correct format' do
get :index, params: params.merge(format: :json)
- json = JSON.parse(response.body)
-
- expect(json.keys).to match_array(%w(enabled_keys available_project_keys public_keys))
- expect(json['enabled_keys'].count).to eq(1)
- expect(json['available_project_keys'].count).to eq(1)
- expect(json['public_keys'].count).to eq(1)
+ expect(json_response.keys).to match_array(%w(enabled_keys available_project_keys public_keys))
+ expect(json_response['enabled_keys'].count).to eq(1)
+ expect(json_response['available_project_keys'].count).to eq(1)
+ expect(json_response['public_keys'].count).to eq(1)
end
end
end
diff --git a/spec/controllers/projects/discussions_controller_spec.rb b/spec/controllers/projects/discussions_controller_spec.rb
index 4c29162cd0f..e30b28a4bd5 100644
--- a/spec/controllers/projects/discussions_controller_spec.rb
+++ b/spec/controllers/projects/discussions_controller_spec.rb
@@ -112,7 +112,7 @@ describe Projects::DiscussionsController do
it "returns the name of the resolving user" do
post :resolve, params: request_params
- expect(JSON.parse(response.body)['resolved_by']['name']).to eq(user.name)
+ expect(json_response['resolved_by']['name']).to eq(user.name)
end
it "returns status 200" do
@@ -135,7 +135,7 @@ describe Projects::DiscussionsController do
it "returns truncated diff lines" do
post :resolve, params: request_params
- expect(JSON.parse(response.body)['truncated_diff_lines']).to be_present
+ expect(json_response['truncated_diff_lines']).to be_present
end
end
end
diff --git a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
index fdef9bc5638..45328482ad7 100644
--- a/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
+++ b/spec/controllers/projects/environments/prometheus_api_controller_spec.rb
@@ -176,7 +176,7 @@ describe Projects::Environments::PrometheusApiController do
def environment_params(params = {})
{
id: environment.id.to_s,
- namespace_id: project.namespace.name,
+ namespace_id: project.namespace.full_path,
project_id: project.name,
proxy_path: 'query',
query: '1'
diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index 4c2c6160c62..ebbbebf1bc0 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::EnvironmentsController do
+ include MetricsDashboardHelpers
+
set(:user) { create(:user) }
set(:project) { create(:project) }
@@ -445,131 +447,186 @@ describe Projects::EnvironmentsController do
end
end
- describe 'metrics_dashboard' do
- context 'when prometheus endpoint is disabled' do
- before do
- stub_feature_flags(environment_metrics_use_prometheus_endpoint: false)
- end
+ describe 'GET #metrics_dashboard' do
+ shared_examples_for 'correctly formatted response' do |status_code|
+ it 'returns a json object with the correct keys' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- it 'responds with status code 403' do
- get :metrics_dashboard, params: environment_params(format: :json)
+ # Exlcude `all_dashboards` to handle separately.
+ found_keys = json_response.keys - ['all_dashboards']
- expect(response).to have_gitlab_http_status(:forbidden)
+ expect(response).to have_gitlab_http_status(status_code)
+ expect(found_keys).to contain_exactly(*expected_keys)
end
end
- shared_examples_for '200 response' do |contains_all_dashboards: false|
+ shared_examples_for '200 response' do
let(:expected_keys) { %w(dashboard status) }
- before do
- expected_keys << 'all_dashboards' if contains_all_dashboards
- end
-
- it 'returns a json representation of the environment dashboard' do
- get :metrics_dashboard, params: environment_params(dashboard_params)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response.keys).to contain_exactly(*expected_keys)
- expect(json_response['dashboard']).to be_an_instance_of(Hash)
- end
+ it_behaves_like 'correctly formatted response', :ok
end
- shared_examples_for 'error response' do |status_code, contains_all_dashboards: false|
+ shared_examples_for 'error response' do |status_code|
let(:expected_keys) { %w(message status) }
- before do
- expected_keys << 'all_dashboards' if contains_all_dashboards
- end
+ it_behaves_like 'correctly formatted response', status_code
+ end
- it 'returns an error response' do
+ shared_examples_for 'includes all dashboards' do
+ it 'includes info for all findable dashboard' do
get :metrics_dashboard, params: environment_params(dashboard_params)
- expect(response).to have_gitlab_http_status(status_code)
- expect(json_response.keys).to contain_exactly(*expected_keys)
+ expect(json_response).to have_key('all_dashboards')
+ expect(json_response['all_dashboards']).to be_an_instance_of(Array)
+ expect(json_response['all_dashboards']).to all( include('path', 'default', 'display_name') )
end
end
- shared_examples_for 'has all dashboards' do
- it 'includes an index of all available dashboards' do
+ shared_examples_for 'the default dashboard' do
+ all_dashboards = Feature.enabled?(:environment_metrics_show_multiple_dashboards)
+
+ it_behaves_like '200 response'
+ it_behaves_like 'includes all dashboards' if all_dashboards
+
+ it 'is the default dashboard' do
get :metrics_dashboard, params: environment_params(dashboard_params)
- expect(json_response.keys).to include('all_dashboards')
- expect(json_response['all_dashboards']).to be_an_instance_of(Array)
- expect(json_response['all_dashboards']).to all( include('path', 'default') )
+ expect(json_response['dashboard']['dashboard']).to eq('Environment metrics')
end
end
- context 'when multiple dashboards is disabled' do
- before do
- stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
- end
+ shared_examples_for 'the specified dashboard' do |expected_dashboard|
+ it_behaves_like '200 response'
+ it_behaves_like 'includes all dashboards'
- let(:dashboard_params) { { format: :json } }
+ it 'has the correct name' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- it_behaves_like '200 response'
+ dashboard_name = json_response['dashboard']['dashboard']
- context 'when the dashboard could not be provided' do
+ # 'Environment metrics' is the default dashboard.
+ expect(dashboard_name).not_to eq('Environment metrics')
+ expect(dashboard_name).to eq(expected_dashboard)
+ end
+
+ context 'when the dashboard cannot not be processed' do
before do
allow(YAML).to receive(:safe_load).and_return({})
end
it_behaves_like 'error response', :unprocessable_entity
end
-
- context 'when a dashboard param is specified' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/not_there_dashboard.yml' } }
-
- it_behaves_like '200 response'
- end
end
- context 'when multiple dashboards is enabled' do
- let(:dashboard_params) { { format: :json } }
+ shared_examples_for 'the default dynamic dashboard' do
+ it_behaves_like '200 response'
- it_behaves_like '200 response', contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ it 'contains only the Memory and CPU charts' do
+ get :metrics_dashboard, params: environment_params(dashboard_params)
- context 'when a dashboard could not be provided' do
- before do
- allow(YAML).to receive(:safe_load).and_return({})
- end
+ dashboard = json_response['dashboard']
+ panel_group = dashboard['panel_groups'].first
+ titles = panel_group['panels'].map { |panel| panel['title'] }
- it_behaves_like 'error response', :unprocessable_entity, contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ 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)']
end
+ end
- context 'when a dashboard param is specified' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } }
+ shared_examples_for 'dashboard can be specified' do
+ context 'when dashboard is specified' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:dashboard_params) { { format: :json, dashboard: dashboard_path } }
+
+ it_behaves_like 'error response', :not_found
- context 'when the dashboard is available' do
+ context 'when the project dashboard is available' do
let(:dashboard_yml) { fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
- let(:dashboard_file) { { '.gitlab/dashboards/test.yml' => dashboard_yml } }
- let(:project) { create(:project, :custom_repo, files: dashboard_file) }
+ let(:project) { project_with_dashboard(dashboard_path, dashboard_yml) }
let(:environment) { create(:environment, name: 'production', project: project) }
- it_behaves_like '200 response', contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ it_behaves_like 'the specified dashboard', 'Test Dashboard'
end
- context 'when the dashboard does not exist' do
- it_behaves_like 'error response', :not_found, contains_all_dashboards: true
- it_behaves_like 'has all dashboards'
+ context 'when the specified dashboard is the default dashboard' do
+ let(:dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH }
+
+ it_behaves_like 'the default dashboard'
end
end
+ end
- context 'when the dashboard is intended for embedding' do
+ shared_examples_for 'dashboard can be embedded' do
+ context 'when the embedded flag is included' do
let(:dashboard_params) { { format: :json, embedded: true } }
- it_behaves_like '200 response'
+ it_behaves_like 'the default dynamic dashboard'
- context 'when a dashboard path is provided' do
- let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml', embedded: true } }
+ context 'when the dashboard is specified' do
+ let(:dashboard_params) { { format: :json, embedded: true, dashboard: '.gitlab/dashboards/fake.yml' } }
- # The dashboard path should simple be ignored.
- it_behaves_like '200 response'
+ # The dashboard param should be ignored.
+ it_behaves_like 'the default dynamic dashboard'
end
end
end
+
+ shared_examples_for 'dashboard cannot be specified' do
+ context 'when dashboard is specified' do
+ let(:dashboard_params) { { format: :json, dashboard: '.gitlab/dashboards/test.yml' } }
+
+ it_behaves_like 'the default dashboard'
+ 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
+ before do
+ stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
+ end
+
+ it_behaves_like 'the default dashboard'
+ 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/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
index 538dbb5ad0b..a493985f8a0 100644
--- a/spec/controllers/projects/find_file_controller_spec.rb
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -53,10 +53,9 @@ describe Projects::FindFileController do
it 'returns an array of file path list' do
go
- json = JSON.parse(response.body)
is_expected.to respond_with(:success)
- expect(json).not_to eq(nil)
- expect(json.length).to be >= 0
+ expect(json_response).not_to eq(nil)
+ expect(json_response.length).to be >= 0
end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index bc5e0b4671e..32d14dce936 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -444,7 +444,7 @@ describe Projects::IssuesController do
it 'renders json with recaptcha_html' do
subject
- expect(JSON.parse(response.body)).to have_key('recaptcha_html')
+ expect(json_response).to have_key('recaptcha_html')
end
end
end
@@ -484,10 +484,8 @@ describe Projects::IssuesController do
it 'returns last edited time' do
go(id: issue.iid)
- data = JSON.parse(response.body)
-
- expect(data).to include('updated_at')
- expect(data['updated_at']).to eq(issue.last_edited_at.to_time.iso8601)
+ expect(json_response).to include('updated_at')
+ expect(json_response['updated_at']).to eq(issue.last_edited_at.to_time.iso8601)
end
end
@@ -520,10 +518,8 @@ describe Projects::IssuesController do
it 'returns the necessary data' do
go(id: issue.iid)
- data = JSON.parse(response.body)
-
- expect(data).to include('title_text', 'description', 'description_text')
- expect(data).to include('task_status', 'lock_version')
+ expect(json_response).to include('title_text', 'description', 'description_text')
+ expect(json_response).to include('task_status', 'lock_version')
end
end
end
@@ -692,9 +688,7 @@ describe Projects::IssuesController do
update_issue(issue_params: { assignee_ids: [assignee.id] })
- body = JSON.parse(response.body)
-
- expect(body['assignees'].first.keys)
+ expect(json_response['assignees'].first.keys)
.to match_array(%w(id name username avatar_url state web_url))
end
end
@@ -1314,7 +1308,7 @@ describe Projects::IssuesController do
it 'filters notes that the user should not see' do
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
- expect(JSON.parse(response.body).count).to eq(1)
+ expect(json_response.count).to eq(1)
end
it 'does not result in N+1 queries' do
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 901402aa5fd..e428aa3c7b7 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
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index 5fefad86ef3..3816e1c7a31 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -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 13a28b738ca..d940d226176 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -112,7 +112,7 @@ describe Projects::MergeRequests::DiffsController do
it 'only renders the diffs for the path given' do
diff_for_path(old_path: existing_path, new_path: existing_path)
- paths = JSON.parse(response.body)["diff_files"].map { |file| file['new_path'] }
+ paths = json_response["diff_files"].map { |file| file['new_path'] }
expect(paths).to include(existing_path)
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 9878f88a395..f11880122b1 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -242,9 +242,7 @@ describe Projects::MergeRequestsController do
update_merge_request({ assignee_ids: [assignee.id] }, format: :json)
- body = JSON.parse(response.body)
-
- expect(body['assignees']).to all(include(*%w(name username avatar_url id state web_url)))
+ expect(json_response['assignees']).to all(include(*%w(name username avatar_url id state web_url)))
end
end
@@ -878,7 +876,7 @@ describe Projects::MergeRequestsController do
expect(control_count).to be <= 137
end
- it 'has no N+1 issues for environments', :request_store, retry: 0 do
+ it 'has no N+1 SQL issues for environments', :request_store, retry: 0 do
# First run to insert test data from lets, which does take up some 30 queries
get_ci_environments_status
@@ -893,17 +891,64 @@ describe Projects::MergeRequestsController do
leeway = 11
expect { get_ci_environments_status }.not_to exceed_all_query_limit(control_count + leeway)
end
+ end
- def get_ci_environments_status(extra_params = {})
- params = {
- namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project,
- id: merge_request.iid,
- format: 'json'
- }
+ context 'when a merge request has multiple environments with deployments' do
+ let(:sha) { merge_request.diff_head_sha }
+ let(:ref) { merge_request.source_branch }
- get :ci_environments_status, params: params.merge(extra_params)
+ let!(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:pipeline) { create(:ci_pipeline, sha: sha, project: project) }
+ let!(:environment) { create(:environment, name: 'env_a', project: project) }
+ let!(:another_environment) { create(:environment, name: 'env_b', project: project) }
+
+ before do
+ merge_request.update_head_pipeline
+
+ create(:deployment, :succeed, environment: environment, sha: sha, ref: ref, deployable: build)
+ create(:deployment, :succeed, environment: another_environment, sha: sha, ref: ref, deployable: build)
+ end
+
+ it 'exposes multiple environment statuses' do
+ get_ci_environments_status
+
+ expect(json_response.count).to eq 2
+ end
+
+ context 'when route map is not present in the project' do
+ it 'does not have N+1 Gitaly requests for environments', :request_store do
+ expect(merge_request).to be_present
+
+ expect { get_ci_environments_status }
+ .to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
end
+
+ context 'when there is route map present in a project' do
+ before do
+ allow_any_instance_of(EnvironmentStatus)
+ .to receive(:has_route_map?)
+ .and_return(true)
+ end
+
+ it 'does not have N+1 Gitaly requests for diff files', :request_store do
+ expect(merge_request.merge_request_diff.merge_request_diff_files).to be_many
+
+ expect { get_ci_environments_status }
+ .to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
+ end
+ end
+
+ def get_ci_environments_status(extra_params = {})
+ params = {
+ namespace_id: merge_request.project.namespace.to_param,
+ project_id: merge_request.project,
+ id: merge_request.iid,
+ format: 'json'
+ }
+
+ get :ci_environments_status, params: params.merge(extra_params)
end
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 1db1963476c..98aea9056dc 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -29,7 +29,7 @@ describe Projects::NotesController do
}
end
- let(:parsed_response) { JSON.parse(response.body).with_indifferent_access }
+ let(:parsed_response) { json_response.with_indifferent_access }
let(:note_json) { parsed_response[:notes].first }
before do
@@ -614,7 +614,7 @@ describe Projects::NotesController do
it "returns the name of the resolving user" do
post :resolve, params: request_params.merge(html: true)
- expect(JSON.parse(response.body)["resolved_by"]).to eq(user.name)
+ expect(json_response["resolved_by"]).to eq(user.name)
end
it "returns status 200" do
diff --git a/spec/controllers/projects/templates_controller_spec.rb b/spec/controllers/projects/templates_controller_spec.rb
index 9e7d34b10c0..d5ef2b0e114 100644
--- a/spec/controllers/projects/templates_controller_spec.rb
+++ b/spec/controllers/projects/templates_controller_spec.rb
@@ -7,7 +7,6 @@ describe Projects::TemplatesController do
let(:user) { create(:user) }
let(:file_path_1) { '.gitlab/issue_templates/issue_template.md' }
let(:file_path_2) { '.gitlab/merge_request_templates/merge_request_template.md' }
- let(:body) { JSON.parse(response.body) }
let!(:file_1) { project.repository.create_file(user, file_path_1, 'issue content', message: 'message', branch_name: 'master') }
let!(:file_2) { project.repository.create_file(user, file_path_2, 'merge request content', message: 'message', branch_name: 'master') }
@@ -17,8 +16,8 @@ describe Projects::TemplatesController do
get(:show, params: { namespace_id: project.namespace, template_type: 'issue', key: 'issue_template', project_id: project }, format: :json)
expect(response.status).to eq(200)
- expect(body['name']).to eq('issue_template')
- expect(body['content']).to eq('issue content')
+ expect(json_response['name']).to eq('issue_template')
+ expect(json_response['content']).to eq('issue content')
end
end
@@ -27,8 +26,8 @@ describe Projects::TemplatesController do
get(:show, params: { namespace_id: project.namespace, template_type: 'merge_request', key: 'merge_request_template', project_id: project }, format: :json)
expect(response.status).to eq(200)
- expect(body['name']).to eq('merge_request_template')
- expect(body['content']).to eq('merge request content')
+ expect(json_response['name']).to eq('merge_request_template')
+ expect(json_response['content']).to eq('merge request content')
end
end
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index f2e0b5e5c1d..fbca1d5740f 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -31,6 +31,47 @@ 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
@@ -103,7 +144,7 @@ describe Projects::WikisController do
it 'renders json in a correct format' do
post :preview_markdown, params: { namespace_id: project.namespace, project_id: project, id: 'page/path', text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
end
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 4e1cac67d23..083a1c1383a 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -740,20 +740,18 @@ describe ProjectsController do
it 'gets a list of branches and tags' do
get :refs, params: { namespace_id: project.namespace, id: project, sort: 'updated_desc' }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body['Branches']).to include('master')
- expect(parsed_body['Tags'].first).to eq('v1.1.0')
- expect(parsed_body['Tags'].last).to eq('v1.0.0')
- expect(parsed_body['Commits']).to be_nil
+ expect(json_response['Branches']).to include('master')
+ expect(json_response['Tags'].first).to eq('v1.1.0')
+ expect(json_response['Tags'].last).to eq('v1.0.0')
+ expect(json_response['Commits']).to be_nil
end
it "gets a list of branches, tags and commits" do
get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body["Branches"]).to include("master")
- expect(parsed_body["Tags"]).to include("v1.0.0")
- expect(parsed_body["Commits"]).to include("123456")
+ expect(json_response["Branches"]).to include("master")
+ expect(json_response["Tags"]).to include("v1.0.0")
+ expect(json_response["Commits"]).to include("123456")
end
context "when preferred language is Japanese" do
@@ -765,10 +763,9 @@ describe ProjectsController do
it "gets a list of branches, tags and commits" do
get :refs, params: { namespace_id: project.namespace, id: project, ref: "123456" }
- parsed_body = JSON.parse(response.body)
- expect(parsed_body["Branches"]).to include("master")
- expect(parsed_body["Tags"]).to include("v1.0.0")
- expect(parsed_body["Commits"]).to include("123456")
+ expect(json_response["Branches"]).to include("master")
+ expect(json_response["Tags"]).to include("v1.0.0")
+ expect(json_response["Commits"]).to include("123456")
end
end
@@ -797,7 +794,7 @@ describe ProjectsController do
it 'renders json in a correct format' do
post :preview_markdown, params: { namespace_id: public_project.namespace, id: public_project, text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
context 'when not authorized' do
@@ -821,8 +818,6 @@ describe ProjectsController do
text: issue.to_reference
}
- json_response = JSON.parse(response.body)
-
expect(json_response['body']).to match(/\##{issue.iid} \(closed\)/)
end
@@ -833,8 +828,6 @@ describe ProjectsController do
text: merge_request.to_reference
}
- json_response = JSON.parse(response.body)
-
expect(json_response['body']).to match(/\!#{merge_request.iid} \(closed\)/)
end
end
diff --git a/spec/controllers/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb
index 586d59c2d09..652533ac49f 100644
--- a/spec/controllers/snippets/notes_controller_spec.rb
+++ b/spec/controllers/snippets/notes_controller_spec.rb
@@ -26,7 +26,7 @@ describe Snippets::NotesController do
end
it "returns not empty array of notes" do
- expect(JSON.parse(response.body)["notes"].empty?).to be_falsey
+ expect(json_response["notes"].empty?).to be_falsey
end
end
@@ -97,7 +97,7 @@ describe Snippets::NotesController do
it "returns 1 note" do
get :index, params: { snippet_id: private_snippet }
- expect(JSON.parse(response.body)['notes'].count).to eq(1)
+ expect(json_response['notes'].count).to eq(1)
end
end
end
@@ -114,7 +114,7 @@ describe Snippets::NotesController do
it "does not return any note" do
get :index, params: { snippet_id: public_snippet }
- expect(JSON.parse(response.body)['notes'].count).to eq(0)
+ expect(json_response['notes'].count).to eq(0)
end
end
end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index 3aba02bf3ff..b0092bc8994 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -622,7 +622,7 @@ describe SnippetsController do
post :preview_markdown, params: { id: snippet, text: '*Markdown* text' }
- expect(JSON.parse(response.body).keys).to match_array(%w(body references))
+ expect(json_response.keys).to match_array(%w(body references))
end
end
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index c3d6ea9cbcd..8b8d4c57000 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -291,7 +291,7 @@ describe UsersController do
it 'response with snippets json data' do
get :snippets, params: { username: user.username }, format: :json
expect(response).to have_gitlab_http_status(200)
- expect(JSON.parse(response.body)).to have_key('html')
+ expect(json_response).to have_key('html')
end
end
diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb
index b1d82b98411..7cb5900f2b7 100644
--- a/spec/factories/ci/bridge.rb
+++ b/spec/factories/ci/bridge.rb
@@ -1,6 +1,6 @@
FactoryBot.define do
factory :ci_bridge, class: Ci::Bridge do
- name ' bridge'
+ name 'bridge'
stage 'test'
stage_idx 0
ref 'master'
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index ab332fc238b..171f5256d2b 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -5,6 +5,8 @@ FactoryBot.define do
cluster_type :project_type
managed true
+ factory :cluster_for_group, traits: [:provided_by_gcp, :group]
+
trait :instance do
cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
end
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index 18a0c2ec731..b67ab955ffc 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -45,5 +45,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/lfs_objects.rb b/spec/factories/lfs_objects.rb
index c909471bb55..08ac7fb69e7 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -14,6 +14,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/pages_domains.rb b/spec/factories/pages_domains.rb
index 3e0baab04ce..e441dfcf229 100644
--- a/spec/factories/pages_domains.rb
+++ b/spec/factories/pages_domains.rb
@@ -166,6 +166,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/projects.rb b/spec/factories/projects.rb
index 743ec322885..0e8810b73a1 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -25,7 +25,9 @@ FactoryBot.define do
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
- pages_access_level ProjectFeature::ENABLED
+ pages_access_level do
+ visibility_level == Gitlab::VisibilityLevel::PUBLIC ? ProjectFeature::ENABLED : ProjectFeature::PRIVATE
+ end
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
@@ -306,34 +308,18 @@ FactoryBot.define do
factory :redmine_project, parent: :project do
has_external_issue_tracker true
- after :create do |project|
- project.create_redmine_service(
- active: true,
- properties: {
- 'project_url' => 'http://redmine/projects/project_name_in_redmine',
- 'issues_url' => 'http://redmine/projects/project_name_in_redmine/issues/:id',
- 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new'
- }
- )
- end
+ redmine_service
end
factory :youtrack_project, parent: :project do
has_external_issue_tracker true
- after :create do |project|
- project.create_youtrack_service(
- active: true,
- properties: {
- 'project_url' => 'http://youtrack/projects/project_guid_in_youtrack',
- 'issues_url' => 'http://youtrack/issues/:id'
- }
- )
- end
+ youtrack_service
end
factory :jira_project, parent: :project do
has_external_issue_tracker true
+
jira_service
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index ecb481ed84a..daf842e3075 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -79,14 +79,12 @@ FactoryBot.define do
trait :issue_tracker do
properties(
project_url: 'http://issue-tracker.example.com',
- issues_url: 'http://issue-tracker.example.com',
+ issues_url: 'http://issue-tracker.example.com/issues/:id',
new_issue_url: 'http://issue-tracker.example.com'
)
end
- factory :jira_cloud_service, class: JiraService do
- project
- active true
+ trait :jira_cloud_service do
properties(
url: 'https://mysite.atlassian.net',
username: 'jira_user',
@@ -100,4 +98,16 @@ FactoryBot.define do
type 'HipchatService'
token 'test_token'
end
+
+ trait :without_properties_callback do
+ after(:build) do |service|
+ allow(service).to receive(:handle_properties)
+ end
+
+ after(:create) do |service|
+ # we have to remove the stub because the behaviour of
+ # handle_properties method is tested after the creation
+ allow(service).to receive(:handle_properties).and_call_original
+ end
+ end
end
diff --git a/spec/factories/services_data.rb b/spec/factories/services_data.rb
index 387e130a743..5a3639895b6 100644
--- a/spec/factories/services_data.rb
+++ b/spec/factories/services_data.rb
@@ -1,18 +1,12 @@
# frozen_string_literal: true
+# these factories should never be called directly, they are used when creating services
FactoryBot.define do
factory :jira_tracker_data do
service
- url 'http://jira.example.com'
- api_url 'http://api-jira.example.com'
- username 'jira_username'
- password 'jira_password'
end
factory :issue_tracker_data do
service
- project_url 'http://issuetracker.example.com'
- issues_url 'http://issues.example.com'
- new_issue_url 'http://new-issue.example.com'
end
end
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 735ca60f7da..a08902c30be 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -90,6 +90,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 +103,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_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 2b6bfa40beb..1c1ca41f633 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -60,6 +60,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..e8764d0a79c 100644
--- a/spec/features/admin/admin_requests_profiles_spec.rb
+++ b/spec/features/admin/admin_requests_profiles_spec.rb
@@ -1,13 +1,15 @@
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 +21,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_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb
index b5323a1c76d..ecd0aab925b 100644
--- a/spec/features/admin/admin_sees_project_statistics_spec.rb
+++ b/spec/features/admin/admin_sees_project_statistics_spec.rb
@@ -15,7 +15,7 @@ describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
- expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes wikis, 0 Bytes build artifacts, 0 Bytes LFS)")
+ expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes)")
end
end
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 4a9037afb43..518b3625348 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -400,35 +400,16 @@ describe 'Admin updates settings' do
.to have_content "The form contains the following error: Polling interval multiplier must be greater than or equal to 0"
end
- context 'When pages_auto_ssl is enabled' do
- before do
- stub_feature_flags(pages_auto_ssl: true)
- visit preferences_admin_application_settings_path
- end
-
- it "Change Pages Let's Encrypt settings" do
- page.within('.as-pages') do
- fill_in 'Email', with: 'my@test.example.com'
- check "I have read and agree to the Let's Encrypt Terms of Service"
- click_button 'Save changes'
- end
-
- expect(current_settings.lets_encrypt_notification_email).to eq 'my@test.example.com'
- expect(current_settings.lets_encrypt_terms_of_service_accepted).to eq true
- end
- end
-
- context 'When pages_auto_ssl is disabled' do
- before do
- stub_feature_flags(pages_auto_ssl: false)
- visit preferences_admin_application_settings_path
+ it "Change Pages Let's Encrypt settings" do
+ visit preferences_admin_application_settings_path
+ page.within('.as-pages') do
+ fill_in 'Email', with: 'my@test.example.com'
+ check "I have read and agree to the Let's Encrypt Terms of Service"
+ click_button 'Save changes'
end
- it "Doesn't show Let's Encrypt options" do
- page.within('.as-pages') do
- expect(page).not_to have_content('Email')
- end
- end
+ expect(current_settings.lets_encrypt_notification_email).to eq 'my@test.example.com'
+ expect(current_settings.lets_encrypt_terms_of_service_accepted).to eq true
end
end
diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb
index 8fb2e37e269..c3310a4a132 100644
--- a/spec/features/dashboard/milestones_spec.rb
+++ b/spec/features/dashboard/milestones_spec.rb
@@ -29,5 +29,19 @@ describe 'Dashboard > Milestones' do
expect(page).to have_content(milestone.title)
expect(page).to have_content(group.name)
end
+
+ describe 'new milestones dropdown', :js do
+ it 'takes user to a new milestone page', :js do
+ find('.new-project-item-select-button').click
+
+ page.within('.select2-results') do
+ first('.select2-result-label').click
+ end
+
+ find('.new-project-item-link').click
+
+ expect(current_path).to eq(new_group_milestone_path(group))
+ end
+ end
end
end
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 5cef5f0521f..676769c25fe 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -85,6 +85,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)
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 9671a4d8c49..bed998a0859 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -56,32 +56,103 @@ 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', :nested_groups do
+ before do
+ allow(Group).to receive(:supports_nested_objects?) { true }
+ end
+
+ it 'allows creating subgroups' do
+ visit path
+
+ expect(page)
+ .to have_css("li[data-text='New subgroup']", visible: false)
+ end
+ end
+
+ context 'when subgroups are not supported' do
+ before do
+ allow(Group).to receive(:supports_nested_objects?) { false }
+ end
+
+ it 'does not allow creating subgroups' do
+ visit path
+
+ expect(page)
+ .not_to have_selector("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', :nested_groups do
+ before do
+ allow(Group).to receive(:supports_nested_objects?) { true }
+ end
+
+ 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
+
+ context 'when subgroups are not supported' do
+ before do
+ allow(Group).to receive(:supports_nested_objects?) { false }
+ end
+
+ it 'does not allow creating subgroups' do
+ visit path
+
+ expect(page)
+ .not_to have_selector("li[data-text='New subgroup']", visible: false)
+ end
end
end
end
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 06cb2e36334..7be5961af09 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -381,7 +381,7 @@ describe 'Issues > Labels bulk assignment' do
if unmark
items.map do |item|
# Make sure we are unmarking the item no matter the state it has currently
- click_link item until find('a', text: item)[:class] == 'label-item'
+ click_link item until find('a', text: item)[:class].include? 'label-item'
end
end
end
diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb
index b4b9a589ba3..4b29f6ee42a 100644
--- a/spec/features/issues/user_comments_on_issue_spec.rb
+++ b/spec/features/issues/user_comments_on_issue_spec.rb
@@ -41,16 +41,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/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index 7008b361394..e3bcaca737e 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -21,4 +21,22 @@ describe 'Mermaid rendering', :js do
expect(page).to have_selector('svg text', text: label)
end
end
+
+ it 'renders linebreaks in Mermaid diagrams' do
+ description = <<~MERMAID
+ ```mermaid
+ graph TD;
+ A(Line 1<br>Line 2)-->B(Line 1<br/>Line 2);
+ C(Line 1<br />Line 2)-->D(Line 1<br />Line 2);
+ ```
+ MERMAID
+
+ project = create(:project, :public)
+ issue = create(:issue, project: project, description: description)
+
+ visit project_issue_path(project, issue)
+
+ expected = '<text><tspan xml:space="preserve" dy="1em" x="1">Line 1</tspan><tspan xml:space="preserve" dy="1em" x="1">Line 2</tspan></text>'
+ expect(page.html.scan(expected).count).to be(4)
+ end
end
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 d05ef2a8f12..2a70cbb2a72 100644
--- a/spec/features/merge_request/user_creates_merge_request_spec.rb
+++ b/spec/features/merge_request/user_creates_merge_request_spec.rb
@@ -78,7 +78,7 @@ describe "User creates a merge request", :js do
click_button("Submit merge request")
- expect(page).to have_content(title).and have_content("Request to merge #{user.namespace.name}:#{source_branch} into master")
+ expect(page).to have_content(title).and have_content("Request to merge #{user.namespace.path}:#{source_branch} into master")
end
end
end
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..3e72b2ce71f 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
@@ -362,14 +362,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/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index e0fa9dbb5fa..e4b02408b49 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -162,6 +162,7 @@ describe 'Projects > Files > User edits files', :js do
expect_fork_status
+ expect(page).to have_css('.ide-sidebar-project-title', text: "#{project2.name} #{user.namespace.full_path}/#{project2.path}")
expect(page).to have_css('.ide .multi-file-tab', text: '.gitignore')
end
diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb
index 7c71b4c52e0..26b94bf58b2 100644
--- a/spec/features/projects/fork_spec.rb
+++ b/spec/features/projects/fork_spec.rb
@@ -50,7 +50,7 @@ describe 'Project fork' do
click_link('New merge request')
end
- expect(current_path).to have_content(/#{user.namespace.name}/i)
+ expect(current_path).to have_content(/#{user.namespace.path}/i)
end
it 'shows avatars when Gravatar is disabled' do
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 033e1afe866..7602935b47e 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -3,300 +3,324 @@ require 'spec_helper'
describe 'New project' do
include Select2Helper
- let(:user) { create(:admin) }
+ context 'as a user' do
+ let(:user) { create(:user) }
- before do
- sign_in(user)
- end
+ before do
+ sign_in(user)
+ end
- it 'shows "New project" page', :js do
- visit new_project_path
+ it 'shows a message if multiple levels are restricted' do
+ Gitlab::CurrentSettings.update!(
+ restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL]
+ )
- expect(page).to have_content('Project name')
- expect(page).to have_content('Project URL')
- expect(page).to have_content('Project slug')
+ visit new_project_path
- find('#import-project-tab').click
+ expect(page).to have_content 'Other visibility settings have been disabled by the administrator.'
+ end
+
+ it 'shows a message if all levels are restricted' do
+ Gitlab::CurrentSettings.update!(
+ restricted_visibility_levels: Gitlab::VisibilityLevel.values
+ )
- expect(page).to have_link('GitHub')
- expect(page).to have_link('Bitbucket')
- expect(page).to have_link('GitLab.com')
- expect(page).to have_link('Google Code')
- expect(page).to have_button('Repo by URL')
- expect(page).to have_link('GitLab export')
+ visit new_project_path
+
+ expect(page).to have_content 'Visibility settings have been disabled by the administrator.'
+ end
end
- describe 'manifest import option' do
+ context 'as an admin' do
+ let(:user) { create(:admin) }
+
before do
+ sign_in(user)
+ end
+
+ it 'shows "New project" page', :js do
visit new_project_path
+ expect(page).to have_content('Project name')
+ expect(page).to have_content('Project URL')
+ expect(page).to have_content('Project slug')
+
find('#import-project-tab').click
- end
- context 'when using postgres', :postgresql do
- it { expect(page).to have_link('Manifest file') }
+ expect(page).to have_link('GitHub')
+ expect(page).to have_link('Bitbucket')
+ expect(page).to have_link('GitLab.com')
+ expect(page).to have_link('Google Code')
+ expect(page).to have_button('Repo by URL')
+ expect(page).to have_link('GitLab export')
end
- context 'when using mysql', :mysql do
- it { expect(page).not_to have_link('Manifest file') }
+ describe 'manifest import option' do
+ before do
+ visit new_project_path
+
+ find('#import-project-tab').click
+ end
+
+ it { expect(page).to have_link('Manifest file') }
end
- end
- context 'Visibility level selector', :js do
- Gitlab::VisibilityLevel.options.each do |key, level|
- it "sets selector to #{key}" do
- stub_application_setting(default_project_visibility: level)
+ context 'Visibility level selector', :js do
+ Gitlab::VisibilityLevel.options.each do |key, level|
+ it "sets selector to #{key}" do
+ stub_application_setting(default_project_visibility: level)
- visit new_project_path
- page.within('#blank-project-pane') do
- expect(find_field("project_visibility_level_#{level}")).to be_checked
+ visit new_project_path
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{level}")).to be_checked
+ end
end
- end
- it "saves visibility level #{level} on validation error" do
- visit new_project_path
+ it "saves visibility level #{level} on validation error" do
+ visit new_project_path
- choose(s_(key))
- click_button('Create project')
- page.within('#blank-project-pane') do
- expect(find_field("project_visibility_level_#{level}")).to be_checked
+ choose(s_(key))
+ click_button('Create project')
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{level}")).to be_checked
+ end
end
end
- end
- context 'when group visibility is private but default is internal' do
- before do
- stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
- end
+ context 'when group visibility is private but default is internal' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
- it 'has private selected' do
- group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
- visit new_project_path(namespace_id: group.id)
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ visit new_project_path(namespace_id: group.id)
- page.within('#blank-project-pane') do
- expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
end
end
- end
- context 'when group visibility is public but user requests private' do
- before do
- stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
- end
+ context 'when group visibility is public but user requests private' do
+ before do
+ stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL)
+ end
- it 'has private selected' do
- group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
- visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
+ it 'has private selected' do
+ group = create(:group, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ visit new_project_path(namespace_id: group.id, project: { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
- page.within('#blank-project-pane') do
- expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ page.within('#blank-project-pane') do
+ expect(find_field("project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).to be_checked
+ end
end
end
end
- end
- context 'Readme selector' do
- it 'shows the initialize with Readme checkbox on "Blank project" tab' do
- visit new_project_path
+ context 'Readme selector' do
+ it 'shows the initialize with Readme checkbox on "Blank project" tab' do
+ visit new_project_path
- expect(page).to have_css('input#project_initialize_with_readme')
- expect(page).to have_content('Initialize repository with a README')
- end
+ expect(page).to have_css('input#project_initialize_with_readme')
+ expect(page).to have_content('Initialize repository with a README')
+ end
- it 'does not show the initialize with Readme checkbox on "Create from template" tab' do
- visit new_project_path
- find('#create-from-template-pane').click
- first('.choose-template').click
+ it 'does not show the initialize with Readme checkbox on "Create from template" tab' do
+ visit new_project_path
+ find('#create-from-template-pane').click
+ first('.choose-template').click
- page.within '.project-fields-form' do
- expect(page).not_to have_css('input#project_initialize_with_readme')
- expect(page).not_to have_content('Initialize repository with a README')
+ page.within '.project-fields-form' do
+ expect(page).not_to have_css('input#project_initialize_with_readme')
+ expect(page).not_to have_content('Initialize repository with a README')
+ end
end
- end
- it 'does not show the initialize with Readme checkbox on "Import project" tab' do
- visit new_project_path
- find('#import-project-tab').click
- first('.js-import-git-toggle-button').click
+ it 'does not show the initialize with Readme checkbox on "Import project" tab' do
+ visit new_project_path
+ find('#import-project-tab').click
+ first('.js-import-git-toggle-button').click
- page.within '.toggle-import-form' do
- expect(page).not_to have_css('input#project_initialize_with_readme')
- expect(page).not_to have_content('Initialize repository with a README')
+ page.within '.toggle-import-form' do
+ expect(page).not_to have_css('input#project_initialize_with_readme')
+ expect(page).not_to have_content('Initialize repository with a README')
+ end
end
end
- end
- context 'Namespace selector' do
- context 'with user namespace' do
- before do
- visit new_project_path
- end
+ context 'Namespace selector' do
+ context 'with user namespace' do
+ before do
+ visit new_project_path
+ end
- it 'selects the user namespace' do
- page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id')
+ it 'selects the user namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id')
- expect(namespace.text).to eq user.username
+ expect(namespace.text).to eq user.username
+ end
end
end
- end
- context 'with group namespace' do
- let(:group) { create(:group, :private) }
+ context 'with group namespace' do
+ let(:group) { create(:group, :private) }
- before do
- group.add_owner(user)
- visit new_project_path(namespace_id: group.id)
- end
+ before do
+ group.add_owner(user)
+ visit new_project_path(namespace_id: group.id)
+ end
- it 'selects the group namespace' do
- page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
+ it 'selects the group namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id option[selected]')
- expect(namespace.text).to eq group.name
+ expect(namespace.text).to eq group.name
+ end
end
end
- end
- context 'with subgroup namespace' do
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
+ context 'with subgroup namespace' do
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
- before do
- group.add_maintainer(user)
- visit new_project_path(namespace_id: subgroup.id)
- end
+ before do
+ group.add_maintainer(user)
+ visit new_project_path(namespace_id: subgroup.id)
+ end
- it 'selects the group namespace' do
- page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
+ it 'selects the group namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id option[selected]')
- expect(namespace.text).to eq subgroup.full_path
+ expect(namespace.text).to eq subgroup.full_path
+ end
end
end
- end
- context 'when changing namespaces dynamically', :js do
- let(:public_group) { create(:group, :public) }
- let(:internal_group) { create(:group, :internal) }
- let(:private_group) { create(:group, :private) }
+ context 'when changing namespaces dynamically', :js do
+ let(:public_group) { create(:group, :public) }
+ let(:internal_group) { create(:group, :internal) }
+ let(:private_group) { create(:group, :private) }
- before do
- public_group.add_owner(user)
- internal_group.add_owner(user)
- private_group.add_owner(user)
- visit new_project_path(namespace_id: public_group.id)
- end
+ before do
+ public_group.add_owner(user)
+ internal_group.add_owner(user)
+ private_group.add_owner(user)
+ visit new_project_path(namespace_id: public_group.id)
+ end
- it 'enables the correct visibility options' do
- select2(user.namespace_id, from: '#project_namespace_id')
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
-
- select2(public_group.id, from: '#project_namespace_id')
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
-
- select2(internal_group.id, from: '#project_namespace_id')
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
-
- select2(private_group.id, from: '#project_namespace_id')
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).to be_disabled
- expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
+ it 'enables the correct visibility options' do
+ select2(user.namespace_id, from: '#project_namespace_id')
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
+
+ select2(public_group.id, from: '#project_namespace_id')
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).not_to be_disabled
+
+ select2(internal_group.id, from: '#project_namespace_id')
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
+
+ select2(private_group.id, from: '#project_namespace_id')
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PRIVATE}")).not_to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::INTERNAL}")).to be_disabled
+ expect(find("#project_visibility_level_#{Gitlab::VisibilityLevel::PUBLIC}")).to be_disabled
+ end
end
end
- end
-
- context 'Import project options', :js do
- before do
- visit new_project_path
- find('#import-project-tab').click
- end
- context 'from git repository url, "Repo by URL"' do
+ context 'Import project options', :js do
before do
- first('.js-import-git-toggle-button').click
+ visit new_project_path
+ find('#import-project-tab').click
end
- it 'does not autocomplete sensitive git repo URL' do
- autocomplete = find('#project_import_url')['autocomplete']
+ context 'from git repository url, "Repo by URL"' do
+ before do
+ first('.js-import-git-toggle-button').click
+ end
- expect(autocomplete).to eq('off')
- end
+ it 'does not autocomplete sensitive git repo URL' do
+ autocomplete = find('#project_import_url')['autocomplete']
- it 'shows import instructions' do
- git_import_instructions = first('.js-toggle-content')
+ expect(autocomplete).to eq('off')
+ end
- expect(git_import_instructions).to be_visible
- expect(git_import_instructions).to have_content 'Git repository URL'
- end
+ it 'shows import instructions' do
+ git_import_instructions = first('.js-toggle-content')
- it 'keeps "Import project" tab open after form validation error' do
- collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace)
+ expect(git_import_instructions).to be_visible
+ expect(git_import_instructions).to have_content 'Git repository URL'
+ end
- fill_in 'project_import_url', with: collision_project.http_url_to_repo
- fill_in 'project_name', with: collision_project.name
+ it 'keeps "Import project" tab open after form validation error' do
+ collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace)
- click_on 'Create project'
+ fill_in 'project_import_url', with: collision_project.http_url_to_repo
+ fill_in 'project_name', with: collision_project.name
- expect(page).to have_css('#import-project-pane.active')
- expect(page).not_to have_css('.toggle-import-form.hide')
- end
- end
+ click_on 'Create project'
- context 'from GitHub' do
- before do
- first('.js-import-github').click
+ expect(page).to have_css('#import-project-pane.active')
+ expect(page).not_to have_css('.toggle-import-form.hide')
+ end
end
- it 'shows import instructions' do
- expect(page).to have_content('Import repositories from GitHub')
- expect(current_path).to eq new_import_github_path
- end
- end
+ context 'from GitHub' do
+ before do
+ first('.js-import-github').click
+ end
- context 'from Google Code' do
- before do
- first('.import_google_code').click
+ it 'shows import instructions' do
+ expect(page).to have_content('Import repositories from GitHub')
+ expect(current_path).to eq new_import_github_path
+ end
end
- it 'shows import instructions' do
- expect(page).to have_content('Import projects from Google Code')
- expect(current_path).to eq new_import_google_code_path
- end
- end
+ context 'from Google Code' do
+ before do
+ first('.import_google_code').click
+ end
- context 'from manifest file', :postgresql do
- before do
- first('.import_manifest').click
+ it 'shows import instructions' do
+ expect(page).to have_content('Import projects from Google Code')
+ expect(current_path).to eq new_import_google_code_path
+ end
end
- it 'shows import instructions' do
- expect(page).to have_content('Manifest file import')
- expect(current_path).to eq new_import_manifest_path
+ context 'from manifest file', :postgresql do
+ before do
+ first('.import_manifest').click
+ end
+
+ it 'shows import instructions' do
+ expect(page).to have_content('Manifest file import')
+ expect(current_path).to eq new_import_manifest_path
+ end
end
end
- end
- context 'Namespace selector' do
- context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do
- let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
+ context 'Namespace selector' do
+ context 'with group with DEVELOPER_MAINTAINER_PROJECT_ACCESS project_creation_level' do
+ let(:group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) }
- before do
- group.add_developer(user)
- visit new_project_path(namespace_id: group.id)
- end
+ before do
+ group.add_developer(user)
+ visit new_project_path(namespace_id: group.id)
+ end
- it 'selects the group namespace' do
- page.within('#blank-project-pane') do
- namespace = find('#project_namespace_id option[selected]')
+ it 'selects the group namespace' do
+ page.within('#blank-project-pane') do
+ namespace = find('#project_namespace_id option[selected]')
- expect(namespace.text).to eq group.full_path
+ expect(namespace.text).to eq group.full_path
+ end
end
end
end
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 2cb2a23b7be..8585e24bc35 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -47,7 +47,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..8623b10562d 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -39,7 +39,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/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 1cc47cd6bd1..7ddd5c12cdf 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'User uses header search field' do
+describe 'User uses header search field', :js do
include FilteredSearchHelpers
let(:project) { create(:project) }
@@ -11,57 +11,12 @@ describe 'User uses header search field' do
sign_in(user)
end
- context 'when user is in a global scope', :js do
+ shared_examples 'search field examples' do
before do
- visit(root_path)
- page.find('#search').click
+ visit(url)
end
- context 'when clicking issues' do
- it 'shows assigned issues' do
- find('.search-input-container .dropdown-menu').click_link('Issues assigned to me')
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'shows created issues' do
- find('.search-input-container .dropdown-menu').click_link("Issues I've created")
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
- end
-
- context 'when clicking merge requests' do
- let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
-
- it 'shows assigned merge requests' do
- find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'shows created merge requests' do
- find('.search-input-container .dropdown-menu').click_link("Merge requests I've created")
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
- end
- end
-
- context 'when user is in a project scope' do
- before do
- visit(project_path(project))
- end
-
- it 'starts searching by pressing the enter key', :js do
+ it 'starts searching by pressing the enter key' do
fill_in('search', with: 'gitlab')
find('#search').native.send_keys(:enter)
@@ -70,30 +25,31 @@ describe 'User uses header search field' do
end
end
- context 'when clicking the search field', :js do
+ context 'when clicking the search field' do
before do
page.find('#search').click
+ wait_for_all_requests
end
it 'shows category search dropdown' do
- expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
+ expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
context 'when clicking issues' do
let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
it 'shows assigned issues' do
- find('.dropdown-menu').click_link('Issues assigned to me')
+ find('.search-input-container .dropdown-menu').click_link('Issues assigned to me')
- expect(page).to have_selector('.filtered-search')
+ expect(page).to have_selector('.issues-list .issue')
expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
it 'shows created issues' do
- find('.dropdown-menu').click_link("Issues I've created")
+ find('.search-input-container .dropdown-menu').click_link("Issues I've created")
- expect(page).to have_selector('.filtered-search')
+ expect(page).to have_selector('.issues-list .issue')
expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
@@ -103,33 +59,77 @@ describe 'User uses header search field' do
let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
it 'shows assigned merge requests' do
- find('.dropdown-menu').click_link('Merge requests assigned to me')
+ find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
- expect(page).to have_selector('.merge-requests-holder')
+ expect(page).to have_selector('.mr-list .merge-request')
expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
it 'shows created merge requests' do
- find('.dropdown-menu').click_link("Merge requests I've created")
+ find('.search-input-container .dropdown-menu').click_link("Merge requests I've created")
- expect(page).to have_selector('.merge-requests-holder')
+ expect(page).to have_selector('.mr-list .merge-request')
expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
end
end
- context 'when entering text into the search field', :js do
+ context 'when entering text into the search field' do
before do
page.within('.search-input-wrap') do
- fill_in('search', with: project.name[0..3])
+ fill_in('search', with: scope_name.first(4))
end
end
it 'does not display the category search dropdown' do
- expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
+ expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
end
end
+
+ context 'when user is in a global scope' do
+ include_examples 'search field examples' do
+ let(:url) { root_path }
+ let(:scope_name) { 'All GitLab' }
+ end
+ end
+
+ context 'when user is in a project scope' do
+ include_examples 'search field examples' do
+ let(:url) { project_path(project) }
+ let(:scope_name) { project.name }
+ end
+ end
+
+ context 'when user is in a group scope' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ include_examples 'search field examples' do
+ let(:url) { group_path(group) }
+ let(:scope_name) { group.name }
+ end
+ end
+
+ context 'when user is in a subgroup scope' do
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, :public, parent: group) }
+ let(:project) { create(:project, namespace: subgroup) }
+
+ before do
+ group.add_owner(user)
+ subgroup.add_owner(user)
+ end
+
+ include_examples 'search field examples' do
+ let(:url) { group_path(subgroup) }
+ let(:scope_name) { subgroup.name }
+ end
+ end
end
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 919859c145a..7c44680e9f7 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -66,7 +66,7 @@ describe 'Triggers', :js do
it 'edit "legacy" trigger and save' do
# Create new trigger without owner association, i.e. Legacy trigger
- create(:ci_trigger, owner: nil, project: @project)
+ create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
# See if the trigger can be edited and description is blank
@@ -127,17 +127,19 @@ describe 'Triggers', :js do
end
describe 'show triggers workflow' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ end
+
it 'contains trigger description placeholder' do
expect(page.find('#trigger_description')['placeholder']).to eq 'Trigger description'
end
- it 'show "legacy" badge for legacy trigger' do
- create(:ci_trigger, owner: nil, project: @project)
+ it 'show "invalid" badge for legacy trigger' do
+ create(:ci_trigger, owner: user, project: @project).update_attribute(:owner, nil)
visit project_settings_ci_cd_path(@project)
- # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
- expect(page.find('.triggers-list')).to have_content 'legacy'
- expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ expect(page.find('.triggers-list')).to have_content 'invalid'
end
it 'show "invalid" badge for trigger with owner having insufficient permissions' do
@@ -149,6 +151,19 @@ describe 'Triggers', :js do
expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
end
+ it 'do not show "Edit" or full token for legacy trigger' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ .update_attribute(:owner, nil)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger not owned shows only first few token chars and doesn't have copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
+ expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
+
+ # See if trigger is non-editable
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
it 'do not show "Edit" or full token for not owned trigger' do
# Create trigger with user different from current_user
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
@@ -175,5 +190,56 @@ describe 'Triggers', :js do
expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ it 'show "legacy" badge for legacy trigger' do
+ create(:ci_trigger, owner: nil, project: @project)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger without owner (i.e. legacy) shows "legacy" badge and is editable
+ expect(page.find('.triggers-list')).to have_content 'legacy'
+ expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ end
+
+ it 'show "invalid" badge for trigger with owner having insufficient permissions' do
+ create(:ci_trigger, owner: guest_user, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger without owner (i.e. legacy) shows "legacy" badge and is non-editable
+ expect(page.find('.triggers-list')).to have_content 'invalid'
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
+ it 'do not show "Edit" or full token for not owned trigger' do
+ # Create trigger with user different from current_user
+ create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger not owned by current_user shows only first few token chars and doesn't have copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content(@project.triggers.first.token[0..3])
+ expect(page.find('.triggers-list')).not_to have_selector('button.btn-clipboard')
+
+ # See if trigger owner name doesn't match with current_user and trigger is non-editable
+ expect(page.find('.triggers-list .trigger-owner')).not_to have_content user.name
+ expect(page.find('.triggers-list')).not_to have_selector('a[title="Edit"]')
+ end
+
+ it 'show "Edit" and full token for owned trigger' do
+ create(:ci_trigger, owner: user, project: @project, description: trigger_title)
+ visit project_settings_ci_cd_path(@project)
+
+ # See if trigger shows full token and has copy-to-clipboard button
+ expect(page.find('.triggers-list')).to have_content @project.triggers.first.token
+ expect(page.find('.triggers-list')).to have_selector('button.btn-clipboard')
+
+ # See if trigger owner name matches with current_user and is editable
+ expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
+ expect(page.find('.triggers-list')).to have_selector('a[title="Edit"]')
+ end
+ end
end
end
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/fixtures/api/schemas/current-board.json b/spec/fixtures/api/schemas/current-board.json
new file mode 100644
index 00000000000..2ddc038e908
--- /dev/null
+++ b/spec/fixtures/api/schemas/current-board.json
@@ -0,0 +1,16 @@
+{
+ "type": "object",
+ "allOf": [
+ { "$ref": "board.json" },
+ {
+ "required" : [
+ "id",
+ "name"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" }
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/phabricator_responses/user.search.json b/spec/fixtures/phabricator_responses/user.search.json
new file mode 100644
index 00000000000..f3ec653a23e
--- /dev/null
+++ b/spec/fixtures/phabricator_responses/user.search.json
@@ -0,0 +1,62 @@
+{
+ "result": {
+ "data": [
+ {
+ "id": 1,
+ "type": "USER",
+ "phid": "PHID-USER-hohoho",
+ "fields": {
+ "username": "jane",
+ "realName": "Jane Doe",
+ "roles": [
+ "admin",
+ "verified",
+ "approved",
+ "activated"
+ ],
+ "dateCreated": 1405970599,
+ "dateModified": 1406705963,
+ "policy": {
+ "view": "public",
+ "edit": "no-one"
+ }
+ },
+ "attachments": {}
+ },
+ {
+ "id": 2,
+ "type": "USER",
+ "phid": "PHID-USER-hihihi",
+ "fields": {
+ "username": "john",
+ "realName": "John Doe",
+ "roles": [
+ "admin",
+ "verified",
+ "approved",
+ "activated"
+ ],
+ "dateCreated": 1403609184,
+ "dateModified": 1559138722,
+ "policy": {
+ "view": "public",
+ "edit": "no-one"
+ }
+ },
+ "attachments": {}
+ }
+ ],
+ "maps": {},
+ "query": {
+ "queryKey": null
+ },
+ "cursor": {
+ "limit": "100",
+ "after": null,
+ "before": null,
+ "order": null
+ }
+ },
+ "error_code": null,
+ "error_info": null
+}
diff --git a/spec/fixtures/security-reports/deprecated/gl-sast-report.json b/spec/fixtures/security-reports/deprecated/gl-sast-report.json
index a85b9be8b5f..2f7e47281e2 100644
--- a/spec/fixtures/security-reports/deprecated/gl-sast-report.json
+++ b/spec/fixtures/security-reports/deprecated/gl-sast-report.json
@@ -838,6 +838,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
"type": "cwe",
"name": "CWE-119",
"value": "119",
@@ -870,6 +875,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - fopen",
+ "value": "fopen"
+ },
+ {
"type": "cwe",
"name": "CWE-362",
"value": "362",
@@ -897,6 +907,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
"type": "cwe",
"name": "CWE-119",
"value": "119",
@@ -930,6 +945,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - strcpy",
+ "value": "strcpy"
+ },
+ {
"type": "cwe",
"name": "CWE-120",
"value": "120",
diff --git a/spec/fixtures/security-reports/master/gl-sast-report.json b/spec/fixtures/security-reports/master/gl-sast-report.json
index 4bef3d22f70..345e1e9f83a 100644
--- a/spec/fixtures/security-reports/master/gl-sast-report.json
+++ b/spec/fixtures/security-reports/master/gl-sast-report.json
@@ -840,6 +840,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
"type": "cwe",
"name": "CWE-119",
"value": "119",
@@ -872,6 +877,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - fopen",
+ "value": "fopen"
+ },
+ {
"type": "cwe",
"name": "CWE-362",
"value": "362",
@@ -899,6 +909,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - char",
+ "value": "char"
+ },
+ {
"type": "cwe",
"name": "CWE-119",
"value": "119",
@@ -932,6 +947,11 @@
},
"identifiers": [
{
+ "type": "flawfinder_func_name",
+ "name": "Flawfinder - strcpy",
+ "value": "strcpy"
+ },
+ {
"type": "cwe",
"name": "CWE-120",
"value": "120",
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 0188d12a57d..7004373be0e 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -412,6 +412,22 @@ describe('Api', () => {
});
});
+ describe('user counts', () => {
+ it('fetches single user counts', done => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/user_counts`;
+ mock.onGet(expectedUrl).reply(200, {
+ merge_requests: 4,
+ });
+
+ Api.userCounts()
+ .then(({ data }) => {
+ expect(data.merge_requests).toBe(4);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('user status', () => {
it('fetches single user status', done => {
const userId = '123456';
diff --git a/spec/frontend/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/boards/services/board_service_spec.js b/spec/frontend/boards/services/board_service_spec.js
index a8a322e7237..e106c2bf1f1 100644
--- a/spec/frontend/boards/services/board_service_spec.js
+++ b/spec/frontend/boards/services/board_service_spec.js
@@ -389,4 +389,163 @@ describe('BoardService', () => {
return expect(BoardService.toggleIssueSubscription(dummyEndpoint)).rejects.toThrow();
});
});
+
+ describe('allBoards', () => {
+ const url = `${endpoints.boardsEndpoint}.json`;
+
+ it('makes a request to fetch all boards', () => {
+ axiosMock.onGet(url).replyOnce(200, dummyResponse);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(service.allBoards()).resolves.toEqual(expectedResponse);
+ });
+
+ it('fails for error response', () => {
+ axiosMock.onGet(url).replyOnce(500);
+
+ return expect(service.allBoards()).rejects.toThrow();
+ });
+ });
+
+ describe('recentBoards', () => {
+ const url = `${endpoints.recentBoardsEndpoint}.json`;
+
+ it('makes a request to fetch all boards', () => {
+ axiosMock.onGet(url).replyOnce(200, dummyResponse);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(service.recentBoards()).resolves.toEqual(expectedResponse);
+ });
+
+ it('fails for error response', () => {
+ axiosMock.onGet(url).replyOnce(500);
+
+ return expect(service.recentBoards()).rejects.toThrow();
+ });
+ });
+
+ describe('createBoard', () => {
+ const labelIds = ['first label', 'second label'];
+ const assigneeId = 'as sign ee';
+ const milestoneId = 'vegetable soup';
+ const board = {
+ labels: labelIds.map(id => ({ id })),
+ assignee: { id: assigneeId },
+ milestone: { id: milestoneId },
+ };
+
+ describe('for existing board', () => {
+ const id = 'skate-board';
+ const url = `${endpoints.boardsEndpoint}/${id}.json`;
+ const expectedRequest = expect.objectContaining({
+ data: JSON.stringify({
+ board: {
+ ...board,
+ id,
+ label_ids: labelIds,
+ assignee_id: assigneeId,
+ milestone_id: milestoneId,
+ },
+ }),
+ });
+
+ let requestSpy;
+
+ beforeEach(() => {
+ requestSpy = jest.fn();
+ axiosMock.onPut(url).replyOnce(config => requestSpy(config));
+ });
+
+ it('makes a request to update the board', () => {
+ requestSpy.mockReturnValue([200, dummyResponse]);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(
+ service.createBoard({
+ ...board,
+ id,
+ }),
+ )
+ .resolves.toEqual(expectedResponse)
+ .then(() => {
+ expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
+ });
+ });
+
+ it('fails for error response', () => {
+ requestSpy.mockReturnValue([500]);
+
+ return expect(
+ service.createBoard({
+ ...board,
+ id,
+ }),
+ )
+ .rejects.toThrow()
+ .then(() => {
+ expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
+ });
+ });
+ });
+
+ describe('for new board', () => {
+ const url = `${endpoints.boardsEndpoint}.json`;
+ const expectedRequest = expect.objectContaining({
+ data: JSON.stringify({
+ board: {
+ ...board,
+ label_ids: labelIds,
+ assignee_id: assigneeId,
+ milestone_id: milestoneId,
+ },
+ }),
+ });
+
+ let requestSpy;
+
+ beforeEach(() => {
+ requestSpy = jest.fn();
+ axiosMock.onPost(url).replyOnce(config => requestSpy(config));
+ });
+
+ it('makes a request to create a new board', () => {
+ requestSpy.mockReturnValue([200, dummyResponse]);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(service.createBoard(board))
+ .resolves.toEqual(expectedResponse)
+ .then(() => {
+ expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
+ });
+ });
+
+ it('fails for error response', () => {
+ requestSpy.mockReturnValue([500]);
+
+ return expect(service.createBoard(board))
+ .rejects.toThrow()
+ .then(() => {
+ expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
+ });
+ });
+ });
+ });
+
+ describe('deleteBoard', () => {
+ const id = 'capsized';
+ const url = `${endpoints.boardsEndpoint}/${id}.json`;
+
+ it('makes a request to delete a boards', () => {
+ axiosMock.onDelete(url).replyOnce(200, dummyResponse);
+ const expectedResponse = expect.objectContaining({ data: dummyResponse });
+
+ return expect(service.deleteBoard({ id })).resolves.toEqual(expectedResponse);
+ });
+
+ it('fails for error response', () => {
+ axiosMock.onDelete(url).replyOnce(500);
+
+ return expect(service.deleteBoard({ id })).rejects.toThrow();
+ });
+ });
});
diff --git a/spec/frontend/commons/nav/user_merge_requests_spec.js b/spec/frontend/commons/nav/user_merge_requests_spec.js
new file mode 100644
index 00000000000..4da6d53557a
--- /dev/null
+++ b/spec/frontend/commons/nav/user_merge_requests_spec.js
@@ -0,0 +1,113 @@
+import {
+ openUserCountsBroadcast,
+ closeUserCountsBroadcast,
+ refreshUserMergeRequestCounts,
+} from '~/commons/nav/user_merge_requests';
+import Api from '~/api';
+
+jest.mock('~/api');
+
+const TEST_COUNT = 1000;
+const MR_COUNT_CLASS = 'merge-requests-count';
+
+describe('User Merge Requests', () => {
+ let channelMock;
+ let newBroadcastChannelMock;
+
+ beforeEach(() => {
+ global.gon.current_user_id = 123;
+
+ channelMock = {
+ postMessage: jest.fn(),
+ close: jest.fn(),
+ };
+ newBroadcastChannelMock = jest.fn().mockImplementation(() => channelMock);
+
+ global.BroadcastChannel = newBroadcastChannelMock;
+ setFixtures(`<div class="${MR_COUNT_CLASS}">0</div>`);
+ });
+
+ const findMRCountText = () => document.body.querySelector(`.${MR_COUNT_CLASS}`).textContent;
+
+ describe('refreshUserMergeRequestCounts', () => {
+ beforeEach(() => {
+ Api.userCounts.mockReturnValue(
+ Promise.resolve({
+ data: { merge_requests: TEST_COUNT },
+ }),
+ );
+ });
+
+ describe('with open broadcast channel', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+
+ return refreshUserMergeRequestCounts();
+ });
+
+ it('updates the top count of merge requests', () => {
+ expect(findMRCountText()).toEqual(TEST_COUNT.toLocaleString());
+ });
+
+ it('calls the API', () => {
+ expect(Api.userCounts).toHaveBeenCalled();
+ });
+
+ it('posts count to BroadcastChannel', () => {
+ expect(channelMock.postMessage).toHaveBeenCalledWith(TEST_COUNT);
+ });
+ });
+
+ describe('without open broadcast channel', () => {
+ beforeEach(() => refreshUserMergeRequestCounts());
+
+ it('does not post anything', () => {
+ expect(channelMock.postMessage).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('openUserCountsBroadcast', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+ });
+
+ it('creates BroadcastChannel that updates DOM on message received', () => {
+ expect(findMRCountText()).toEqual('0');
+
+ channelMock.onmessage({ data: TEST_COUNT });
+
+ expect(findMRCountText()).toEqual(TEST_COUNT.toLocaleString());
+ });
+
+ it('closes if called while already open', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+
+ openUserCountsBroadcast();
+
+ expect(channelMock.close).toHaveBeenCalled();
+ });
+ });
+
+ describe('closeUserCountsBroadcast', () => {
+ describe('when not opened', () => {
+ it('does nothing', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when opened', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+ });
+
+ it('closes', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+
+ closeUserCountsBroadcast();
+
+ expect(channelMock.close).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap
index a241c764df7..47bdc677068 100644
--- a/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap
+++ b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap
@@ -2,7 +2,7 @@
exports[`Confidential merge request project form group component renders empty state when response is empty 1`] = `
<div
- class="form-group"
+ class="confidential-merge-request-fork-group form-group"
>
<label>
Project
@@ -19,7 +19,7 @@ exports[`Confidential merge request project form group component renders empty s
<br />
<span>
- To protect this issues confidentiality,
+ To protect this issue's confidentiality,
<a
class="help-link"
href="https://test.com"
@@ -30,7 +30,7 @@ exports[`Confidential merge request project form group component renders empty s
</span>
<gllink-stub
- class="help-link"
+ class="w-auto p-0 d-inline-block text-primary bg-transparent"
href="/help"
target="_blank"
>
@@ -52,7 +52,7 @@ exports[`Confidential merge request project form group component renders empty s
exports[`Confidential merge request project form group component renders fork dropdown 1`] = `
<div
- class="form-group"
+ class="confidential-merge-request-fork-group form-group"
>
<label>
Project
@@ -69,7 +69,7 @@ exports[`Confidential merge request project form group component renders fork dr
<br />
<span>
- To protect this issues confidentiality,
+ To protect this issue's confidentiality,
<a
class="help-link"
href="https://test.com"
@@ -80,7 +80,7 @@ exports[`Confidential merge request project form group component renders fork dr
</span>
<gllink-stub
- class="help-link"
+ class="w-auto p-0 d-inline-block text-primary bg-transparent"
href="/help"
target="_blank"
>
diff --git a/spec/frontend/create_merge_request_dropdown_spec.js b/spec/frontend/create_merge_request_dropdown_spec.js
index 6e41fdabdce..dcc6fa96d18 100644
--- a/spec/frontend/create_merge_request_dropdown_spec.js
+++ b/spec/frontend/create_merge_request_dropdown_spec.js
@@ -1,6 +1,7 @@
import axios from '~/lib/utils/axios_utils';
import MockAdapter from 'axios-mock-adapter';
import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
+import confidentialState from '~/confidential_merge_request/state';
import { TEST_HOST } from './helpers/test_constants';
describe('CreateMergeRequestDropdown', () => {
@@ -66,4 +67,37 @@ describe('CreateMergeRequestDropdown', () => {
);
});
});
+
+ describe('enable', () => {
+ beforeEach(() => {
+ dropdown.createMergeRequestButton.classList.add('disabled');
+ });
+
+ afterEach(() => {
+ confidentialState.selectedProject = {};
+ });
+
+ it('enables button when not confidential issue', () => {
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).not.toContain('disabled');
+ });
+
+ it('enables when can create confidential issue', () => {
+ document.querySelector('.js-create-mr').setAttribute('data-is-confidential', 'true');
+ confidentialState.selectedProject = { name: 'test' };
+
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).not.toContain('disabled');
+ });
+
+ it('does not enable when can not create confidential issue', () => {
+ document.querySelector('.js-create-mr').setAttribute('data-is-confidential', 'true');
+
+ dropdown.enable();
+
+ expect(dropdown.createMergeRequestButton.classList).toContain('disabled');
+ });
+ });
});
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/error_tracking_settings/mock.js b/spec/frontend/error_tracking_settings/mock.js
index 42233f82d54..8c5bfd08beb 100644
--- a/spec/frontend/error_tracking_settings/mock.js
+++ b/spec/frontend/error_tracking_settings/mock.js
@@ -1,5 +1,5 @@
import createStore from '~/error_tracking_settings/store';
-import { TEST_HOST } from '../helpers/test_constants';
+import { TEST_HOST } from 'helpers/test_constants';
const defaultStore = createStore();
diff --git a/spec/frontend/filterable_list_spec.js b/spec/frontend/filterable_list_spec.js
new file mode 100644
index 00000000000..67d18611661
--- /dev/null
+++ b/spec/frontend/filterable_list_spec.js
@@ -0,0 +1,53 @@
+import FilterableList from '~/filterable_list';
+import { getJSONFixture, setHTMLFixture } from './helpers/fixtures';
+
+describe('FilterableList', () => {
+ let List;
+ let form;
+ let filter;
+ let holder;
+
+ beforeEach(() => {
+ setHTMLFixture(`
+ <form id="project-filter-form">
+ <input name="name" class="js-projects-list-filter" />
+ </div>
+ <div class="js-projects-list-holder"></div>
+ `);
+ getJSONFixture('static/projects.json');
+ form = document.querySelector('form#project-filter-form');
+ filter = document.querySelector('.js-projects-list-filter');
+ holder = document.querySelector('.js-projects-list-holder');
+ List = new FilterableList(form, filter, holder);
+ });
+
+ it('processes input parameters', () => {
+ expect(List.filterForm).toEqual(form);
+ expect(List.listFilterElement).toEqual(filter);
+ expect(List.listHolderElement).toEqual(holder);
+ });
+
+ describe('getPagePath', () => {
+ it('returns properly constructed base endpoint', () => {
+ List.filterForm.action = '/foo/bar/';
+ List.listFilterElement.value = 'blah';
+
+ expect(List.getPagePath()).toEqual('/foo/bar/?name=blah');
+ });
+
+ it('properly appends custom parameters to existing URL', () => {
+ List.filterForm.action = '/foo/bar?alpha=beta';
+ List.listFilterElement.value = 'blah';
+
+ expect(List.getPagePath()).toEqual('/foo/bar?alpha=beta&name=blah');
+ });
+ });
+
+ describe('getFilterEndpoint', () => {
+ it('returns getPagePath by default', () => {
+ jest.spyOn(List, 'getPagePath').mockReturnValue('blah/blah/foo');
+
+ expect(List.getFilterEndpoint()).toEqual(List.getPagePath());
+ });
+ });
+});
diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/frontend/fixtures/abuse_reports.rb
index e0aaecf626a..21356390cae 100644
--- a/spec/javascripts/fixtures/abuse_reports.rb
+++ b/spec/frontend/fixtures/abuse_reports.rb
@@ -21,6 +21,6 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll
it 'abuse_reports/abuse_reports_list.html' do
get :index
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/frontend/fixtures/admin_users.rb
index 22a5de66577..0209594dadc 100644
--- a/spec/javascripts/fixtures/admin_users.rb
+++ b/spec/frontend/fixtures/admin_users.rb
@@ -23,6 +23,6 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
get :new
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/frontend/fixtures/application_settings.rb
index d4651fa6ece..38a060580c1 100644
--- a/spec/javascripts/fixtures/application_settings.rb
+++ b/spec/frontend/fixtures/application_settings.rb
@@ -28,6 +28,6 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c
get :show
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/frontend/fixtures/autocomplete_sources.rb
index b20a0159d7d..9e04328e2b9 100644
--- a/spec/javascripts/fixtures/autocomplete_sources.rb
+++ b/spec/frontend/fixtures/autocomplete_sources.rb
@@ -34,6 +34,6 @@ describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type:
type_id: issue.id
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/frontend/fixtures/blob.rb
index 07670552cd5..ce5030efbf8 100644
--- a/spec/javascripts/fixtures/blob.rb
+++ b/spec/frontend/fixtures/blob.rb
@@ -29,6 +29,6 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
id: 'add-ipython-files/files/ipython/basic.ipynb'
})
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/frontend/fixtures/boards.rb
index 5835721d3d5..f257d80390f 100644
--- a/spec/javascripts/fixtures/boards.rb
+++ b/spec/frontend/fixtures/boards.rb
@@ -23,6 +23,6 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller
project_id: project
})
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/frontend/fixtures/branches.rb
index 204aa9b7c7a..197fe42c52a 100644
--- a/spec/javascripts/fixtures/branches.rb
+++ b/spec/frontend/fixtures/branches.rb
@@ -27,6 +27,6 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
project_id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/frontend/fixtures/clusters.rb
index 1076404e0e3..f15ef010807 100644
--- a/spec/javascripts/fixtures/clusters.rb
+++ b/spec/frontend/fixtures/clusters.rb
@@ -29,6 +29,6 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
id: cluster
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/frontend/fixtures/commit.rb
index ff9a4bc1adc..a328c455356 100644
--- a/spec/javascripts/fixtures/commit.rb
+++ b/spec/frontend/fixtures/commit.rb
@@ -28,6 +28,6 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller
get :show, params: params
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/frontend/fixtures/deploy_keys.rb
index 38eab853da2..fca233c6f59 100644
--- a/spec/javascripts/fixtures/deploy_keys.rb
+++ b/spec/frontend/fixtures/deploy_keys.rb
@@ -38,6 +38,6 @@ describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :control
project_id: project
}, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/frontend/fixtures/groups.rb
index 4d0afc3ce1a..c1bb2d43332 100644
--- a/spec/javascripts/fixtures/groups.rb
+++ b/spec/frontend/fixtures/groups.rb
@@ -21,7 +21,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
it 'groups/edit.html' do
get :edit, params: { id: group }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -29,7 +29,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
it 'groups/ci_cd_settings.html' do
get :show, params: { group_id: group }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/frontend/fixtures/issues.rb
index d8d77f767de..b5eb38e0023 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/frontend/fixtures/issues.rb
@@ -48,7 +48,7 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
project_id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
private
@@ -60,7 +60,7 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
id: issue.to_param
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -117,6 +117,6 @@ describe API::Issues, '(JavaScript fixtures)', type: :request do
get_related_merge_requests(project.id, issue.iid, user)
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/frontend/fixtures/jobs.rb
index 46ccd6f8c8a..a3a7759c85b 100644
--- a/spec/javascripts/fixtures/jobs.rb
+++ b/spec/frontend/fixtures/jobs.rb
@@ -39,7 +39,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
id: build_with_artifacts.to_param
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'jobs/delayed.json' do
@@ -49,6 +49,6 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
id: delayed_job.to_param
}, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/labels.rb b/spec/frontend/fixtures/labels.rb
index 4d1b7317274..a312287970f 100644
--- a/spec/javascripts/fixtures/labels.rb
+++ b/spec/frontend/fixtures/labels.rb
@@ -35,7 +35,7 @@ describe 'Labels (JavaScript fixtures)' do
group_id: group
}, format: 'json'
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -52,7 +52,7 @@ describe 'Labels (JavaScript fixtures)' do
project_id: project
}, format: 'json'
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
index 05860be2291..88706e96676 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -129,6 +129,6 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
id: merge_request.to_param
}, format: :html
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/frontend/fixtures/merge_requests_diffs.rb
index 03b9b713fd8..b633a0495a6 100644
--- a/spec/javascripts/fixtures/merge_requests_diffs.rb
+++ b/spec/frontend/fixtures/merge_requests_diffs.rb
@@ -1,4 +1,3 @@
-
require 'spec_helper'
describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
@@ -65,6 +64,6 @@ describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type
**extra_params
}, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/frontend/fixtures/pipeline_schedules.rb
index aecd56e6198..a70091a3919 100644
--- a/spec/javascripts/fixtures/pipeline_schedules.rb
+++ b/spec/frontend/fixtures/pipeline_schedules.rb
@@ -28,7 +28,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
id: pipeline_schedule.id
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'pipeline_schedules/edit_with_variables.html' do
@@ -38,6 +38,6 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
id: pipeline_schedule_populated.id
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/frontend/fixtures/pipelines.rb
index 6b6b0eefab9..ed57eb0aa80 100644
--- a/spec/javascripts/fixtures/pipelines.rb
+++ b/spec/frontend/fixtures/pipelines.rb
@@ -29,6 +29,6 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll
project_id: project
}, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb
index 94c59207898..b6c29003e57 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/frontend/fixtures/projects.rb
@@ -18,6 +18,8 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
end
before do
+ stub_licensed_features(variable_environment_scope: true)
+
project.add_maintainer(admin)
sign_in(admin)
allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
@@ -34,7 +36,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'projects/overview.html' do
@@ -43,7 +45,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
id: project_with_repo
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'projects/edit.html' do
@@ -52,7 +54,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -63,7 +65,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
project_id: project
}
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'projects/ci_cd_settings_with_variables.html' do
@@ -75,7 +77,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
project_id: project_variable_populated
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/frontend/fixtures/prometheus_service.rb
index f3171fdd97b..93ee81120d7 100644
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ b/spec/frontend/fixtures/prometheus_service.rb
@@ -29,6 +29,6 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
id: service.to_param
}
- expect(response).to be_success
+ 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/javascripts/fixtures/search.rb b/spec/frontend/fixtures/search.rb
index 22fc546d761..c26c6998ae9 100644
--- a/spec/javascripts/fixtures/search.rb
+++ b/spec/frontend/fixtures/search.rb
@@ -12,6 +12,6 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do
it 'search/show.html' do
get :show
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/services.rb b/spec/frontend/fixtures/services.rb
index 2237702ccca..ee1e088f158 100644
--- a/spec/javascripts/fixtures/services.rb
+++ b/spec/frontend/fixtures/services.rb
@@ -29,6 +29,6 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
id: service.to_param
}
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/frontend/fixtures/sessions.rb
index 92b74c01c89..18574ea06b5 100644
--- a/spec/javascripts/fixtures/sessions.rb
+++ b/spec/frontend/fixtures/sessions.rb
@@ -19,7 +19,7 @@ describe 'Sessions (JavaScript fixtures)' do
it 'sessions/new.html' do
get :new
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/frontend/fixtures/snippet.rb
index ace84b14eb7..23bcdb47ac6 100644
--- a/spec/javascripts/fixtures/snippet.rb
+++ b/spec/frontend/fixtures/snippet.rb
@@ -28,6 +28,6 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
get(:show, params: { id: snippet.to_param })
- expect(response).to be_success
+ 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/javascripts/fixtures/todos.rb b/spec/frontend/fixtures/todos.rb
index d0c8a6eca01..a7c183d2414 100644
--- a/spec/javascripts/fixtures/todos.rb
+++ b/spec/frontend/fixtures/todos.rb
@@ -29,7 +29,7 @@ describe 'Todos (JavaScript fixtures)' do
it 'todos/todos.html' do
get :index
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -48,7 +48,7 @@ describe 'Todos (JavaScript fixtures)' do
issuable_id: issue_2.id
}, format: 'json'
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/frontend/fixtures/u2f.rb
index f52832b6efb..8ecbc0390cd 100644
--- a/spec/javascripts/fixtures/u2f.rb
+++ b/spec/frontend/fixtures/u2f.rb
@@ -23,7 +23,7 @@ context 'U2F' do
post :create, params: { user: { login: user.username, password: user.password } }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -38,7 +38,7 @@ context 'U2F' do
it 'u2f/register.html' do
get :show
- expect(response).to be_success
+ 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/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/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js
index 50041667a61..77da3390918 100644
--- a/spec/frontend/issue_show/components/pinned_links_spec.js
+++ b/spec/frontend/issue_show/components/pinned_links_spec.js
@@ -5,10 +5,6 @@ import PinnedLinks from '~/issue_show/components/pinned_links.vue';
const localVue = createLocalVue();
const plainZoomUrl = 'https://zoom.us/j/123456789';
-const vanityZoomUrl = 'https://gitlab.zoom.us/j/123456789';
-const startZoomUrl = 'https://zoom.us/s/123456789';
-const personalZoomUrl = 'https://zoom.us/my/hunter-zoloman';
-const randomUrl = 'https://zoom.us.com';
describe('PinnedLinks', () => {
let wrapper;
@@ -27,7 +23,7 @@ describe('PinnedLinks', () => {
localVue,
sync: false,
propsData: {
- descriptionHtml: '',
+ zoomMeetingUrl: null,
...props,
},
});
@@ -35,55 +31,15 @@ describe('PinnedLinks', () => {
it('displays Zoom link', () => {
createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`,
+ zoomMeetingUrl: `<a href="${plainZoomUrl}">Zoom</a>`,
});
expect(link.text).toBe('Join Zoom meeting');
});
- it('detects plain Zoom link', () => {
+ it('does not render if there are no links', () => {
createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(plainZoomUrl);
- });
-
- it('detects vanity Zoom link', () => {
- createComponent({
- descriptionHtml: `<a href="${vanityZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(vanityZoomUrl);
- });
-
- it('detects Zoom start meeting link', () => {
- createComponent({
- descriptionHtml: `<a href="${startZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(startZoomUrl);
- });
-
- it('detects personal Zoom room link', () => {
- createComponent({
- descriptionHtml: `<a href="${personalZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(personalZoomUrl);
- });
-
- it('only renders final Zoom link in description', () => {
- createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a><a href="${vanityZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(vanityZoomUrl);
- });
-
- it('does not render for other links', () => {
- createComponent({
- descriptionHtml: `<a href="${randomUrl}">Some other link</a>`,
+ zoomMeetingUrl: null,
});
expect(wrapper.find(GlLink).exists()).toBe(false);
diff --git a/spec/frontend/mocks/ce/lib/utils/axios_utils.js b/spec/frontend/mocks/ce/lib/utils/axios_utils.js
new file mode 100644
index 00000000000..b4065626b09
--- /dev/null
+++ b/spec/frontend/mocks/ce/lib/utils/axios_utils.js
@@ -0,0 +1,15 @@
+const axios = jest.requireActual('~/lib/utils/axios_utils').default;
+
+axios.isMock = true;
+
+// Fail tests for unmocked requests
+axios.defaults.adapter = config => {
+ const message =
+ `Unexpected unmocked request: ${JSON.stringify(config, null, 2)}\n` +
+ 'Consider using the `axios-mock-adapter` in tests.';
+ const error = new Error(message);
+ error.config = config;
+ throw error;
+};
+
+export default axios;
diff --git a/spec/frontend/mocks/mocks_helper.js b/spec/frontend/mocks/mocks_helper.js
new file mode 100644
index 00000000000..21c032cd3c9
--- /dev/null
+++ b/spec/frontend/mocks/mocks_helper.js
@@ -0,0 +1,60 @@
+/**
+ * @module
+ *
+ * This module implements auto-injected manual mocks that are cleaner than Jest's approach.
+ *
+ * See https://docs.gitlab.com/ee/development/testing_guide/frontend_testing.html
+ */
+
+import fs from 'fs';
+import path from 'path';
+
+import readdir from 'readdir-enhanced';
+
+const MAX_DEPTH = 20;
+const prefixMap = [
+ // E.g. the mock ce/foo/bar maps to require path ~/foo/bar
+ { mocksRoot: 'ce', requirePrefix: '~' },
+ // { mocksRoot: 'ee', requirePrefix: 'ee' }, // We'll deal with EE-specific mocks later
+ { mocksRoot: 'node', requirePrefix: '' },
+ // { mocksRoot: 'virtual', requirePrefix: '' }, // We'll deal with virtual mocks later
+];
+
+const mockFileFilter = stats => stats.isFile() && stats.path.endsWith('.js');
+
+const getMockFiles = root => readdir.sync(root, { deep: MAX_DEPTH, filter: mockFileFilter });
+
+// Function that performs setting a mock. This has to be overridden by the unit test, because
+// jest.setMock can't be overwritten across files.
+// Use require() because jest.setMock expects the CommonJS exports object
+const defaultSetMock = (srcPath, mockPath) =>
+ jest.mock(srcPath, () => jest.requireActual(mockPath));
+
+// eslint-disable-next-line import/prefer-default-export
+export const setupManualMocks = function setupManualMocks(setMock = defaultSetMock) {
+ prefixMap.forEach(({ mocksRoot, requirePrefix }) => {
+ const mocksRootAbsolute = path.join(__dirname, mocksRoot);
+ if (!fs.existsSync(mocksRootAbsolute)) {
+ return;
+ }
+
+ getMockFiles(path.join(__dirname, mocksRoot)).forEach(mockPath => {
+ const mockPathNoExt = mockPath.substring(0, mockPath.length - path.extname(mockPath).length);
+ const sourcePath = path.join(requirePrefix, mockPathNoExt);
+ const mockPathRelative = `./${path.join(mocksRoot, mockPathNoExt)}`;
+
+ try {
+ setMock(sourcePath, mockPathRelative);
+ } catch (e) {
+ if (e.message.includes('Could not locate module')) {
+ // The corresponding mocked module doesn't exist. Raise a better error.
+ // Eventualy, we may support virtual mocks (mocks whose path doesn't directly correspond
+ // to a module, like with the `ee_else_ce` prefix).
+ throw new Error(
+ `A manual mock was defined for module ${sourcePath}, but the module doesn't exist!`,
+ );
+ }
+ }
+ });
+ });
+};
diff --git a/spec/frontend/mocks/mocks_helper_spec.js b/spec/frontend/mocks/mocks_helper_spec.js
new file mode 100644
index 00000000000..34be110a7e3
--- /dev/null
+++ b/spec/frontend/mocks/mocks_helper_spec.js
@@ -0,0 +1,147 @@
+/* eslint-disable global-require, promise/catch-or-return */
+
+import path from 'path';
+
+import axios from '~/lib/utils/axios_utils';
+
+const absPath = path.join.bind(null, __dirname);
+
+jest.mock('fs');
+jest.mock('readdir-enhanced');
+
+describe('mocks_helper.js', () => {
+ let setupManualMocks;
+ const setMock = jest.fn().mockName('setMock');
+ let fs;
+ let readdir;
+
+ beforeAll(() => {
+ jest.resetModules();
+ jest.setMock = jest.fn().mockName('jest.setMock');
+ fs = require('fs');
+ readdir = require('readdir-enhanced');
+
+ // We need to provide setupManualMocks with a mock function that pretends to do the setup of
+ // the mock. This is because we can't mock jest.setMock across files.
+ setupManualMocks = () => require('./mocks_helper').setupManualMocks(setMock);
+ });
+
+ afterEach(() => {
+ fs.existsSync.mockReset();
+ readdir.sync.mockReset();
+ setMock.mockReset();
+ });
+
+ it('enumerates through mock file roots', () => {
+ setupManualMocks();
+ expect(fs.existsSync).toHaveBeenCalledTimes(2);
+ expect(fs.existsSync).toHaveBeenNthCalledWith(1, absPath('ce'));
+ expect(fs.existsSync).toHaveBeenNthCalledWith(2, absPath('node'));
+
+ expect(readdir.sync).toHaveBeenCalledTimes(0);
+ });
+
+ it("doesn't traverse the directory tree infinitely", () => {
+ fs.existsSync.mockReturnValue(true);
+ readdir.sync.mockReturnValue([]);
+ setupManualMocks();
+
+ readdir.mock.calls.forEach(call => {
+ expect(call[1].deep).toBeLessThan(100);
+ });
+ });
+
+ it('sets up mocks for CE (the ~/ prefix)', () => {
+ fs.existsSync.mockImplementation(root => root.endsWith('ce'));
+ readdir.sync.mockReturnValue(['root.js', 'lib/utils/util.js']);
+ setupManualMocks();
+
+ expect(readdir.sync).toHaveBeenCalledTimes(1);
+ expect(readdir.sync.mock.calls[0][0]).toBe(absPath('ce'));
+
+ expect(setMock).toHaveBeenCalledTimes(2);
+ expect(setMock).toHaveBeenNthCalledWith(1, '~/root', './ce/root');
+ expect(setMock).toHaveBeenNthCalledWith(2, '~/lib/utils/util', './ce/lib/utils/util');
+ });
+
+ it('sets up mocks for node_modules', () => {
+ fs.existsSync.mockImplementation(root => root.endsWith('node'));
+ readdir.sync.mockReturnValue(['jquery', '@babel/core']);
+ setupManualMocks();
+
+ expect(readdir.sync).toHaveBeenCalledTimes(1);
+ expect(readdir.sync.mock.calls[0][0]).toBe(absPath('node'));
+
+ expect(setMock).toHaveBeenCalledTimes(2);
+ expect(setMock).toHaveBeenNthCalledWith(1, 'jquery', './node/jquery');
+ expect(setMock).toHaveBeenNthCalledWith(2, '@babel/core', './node/@babel/core');
+ });
+
+ it('sets up mocks for all roots', () => {
+ const files = {
+ [absPath('ce')]: ['root', 'lib/utils/util'],
+ [absPath('node')]: ['jquery', '@babel/core'],
+ };
+
+ fs.existsSync.mockReturnValue(true);
+ readdir.sync.mockImplementation(root => files[root]);
+ setupManualMocks();
+
+ expect(readdir.sync).toHaveBeenCalledTimes(2);
+ expect(readdir.sync.mock.calls[0][0]).toBe(absPath('ce'));
+ expect(readdir.sync.mock.calls[1][0]).toBe(absPath('node'));
+
+ expect(setMock).toHaveBeenCalledTimes(4);
+ expect(setMock).toHaveBeenNthCalledWith(1, '~/root', './ce/root');
+ expect(setMock).toHaveBeenNthCalledWith(2, '~/lib/utils/util', './ce/lib/utils/util');
+ expect(setMock).toHaveBeenNthCalledWith(3, 'jquery', './node/jquery');
+ expect(setMock).toHaveBeenNthCalledWith(4, '@babel/core', './node/@babel/core');
+ });
+
+ it('fails when given a virtual mock', () => {
+ fs.existsSync.mockImplementation(p => p.endsWith('ce'));
+ readdir.sync.mockReturnValue(['virtual', 'shouldntBeImported']);
+ setMock.mockImplementation(() => {
+ throw new Error('Could not locate module');
+ });
+
+ expect(setupManualMocks).toThrow(
+ new Error("A manual mock was defined for module ~/virtual, but the module doesn't exist!"),
+ );
+
+ expect(readdir.sync).toHaveBeenCalledTimes(1);
+ expect(readdir.sync.mock.calls[0][0]).toBe(absPath('ce'));
+ });
+
+ describe('auto-injection', () => {
+ it('handles ambiguous paths', () => {
+ jest.isolateModules(() => {
+ const axios2 = require('../../../app/assets/javascripts/lib/utils/axios_utils').default;
+ expect(axios2.isMock).toBe(true);
+ });
+ });
+
+ it('survives jest.isolateModules()', done => {
+ jest.isolateModules(() => {
+ const axios2 = require('~/lib/utils/axios_utils').default;
+ expect(axios2.get('http://gitlab.com'))
+ .rejects.toThrow('Unexpected unmocked request')
+ .then(done);
+ });
+ });
+
+ it('can be unmocked and remocked', () => {
+ jest.dontMock('~/lib/utils/axios_utils');
+ jest.resetModules();
+ const axios2 = require('~/lib/utils/axios_utils').default;
+ expect(axios2).not.toBe(axios);
+ expect(axios2.isMock).toBeUndefined();
+
+ jest.doMock('~/lib/utils/axios_utils');
+ jest.resetModules();
+ const axios3 = require('~/lib/utils/axios_utils').default;
+ expect(axios3).not.toBe(axios2);
+ expect(axios3.isMock).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/mocks/node/jquery.js b/spec/frontend/mocks/node/jquery.js
new file mode 100644
index 00000000000..34a25772f67
--- /dev/null
+++ b/spec/frontend/mocks/node/jquery.js
@@ -0,0 +1,13 @@
+/* eslint-disable import/no-commonjs */
+
+const $ = jest.requireActual('jquery');
+
+// Fail tests for unmocked requests
+$.ajax = () => {
+ throw new Error(
+ 'Unexpected unmocked jQuery.ajax() call! Make sure to mock jQuery.ajax() in tests.',
+ );
+};
+
+// jquery is not an ES6 module
+module.exports = $;
diff --git a/spec/frontend/mocks_spec.js b/spec/frontend/mocks_spec.js
new file mode 100644
index 00000000000..2d2324120fd
--- /dev/null
+++ b/spec/frontend/mocks_spec.js
@@ -0,0 +1,13 @@
+import $ from 'jquery';
+import axios from '~/lib/utils/axios_utils';
+
+describe('Mock auto-injection', () => {
+ describe('mocks', () => {
+ it('~/lib/utils/axios_utils', () =>
+ expect(axios.get('http://gitlab.com')).rejects.toThrow('Unexpected unmocked request'));
+
+ it('jQuery.ajax()', () => {
+ expect($.ajax).toThrow('Unexpected unmocked');
+ });
+ });
+});
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/operation_settings/components/external_dashboard_spec.js b/spec/frontend/operation_settings/components/external_dashboard_spec.js
index a881de8fbfe..39d7c19e731 100644
--- a/spec/frontend/operation_settings/components/external_dashboard_spec.js
+++ b/spec/frontend/operation_settings/components/external_dashboard_spec.js
@@ -7,7 +7,6 @@ import { refreshCurrentPage } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import { TEST_HOST } from 'helpers/test_constants';
-jest.mock('~/lib/utils/axios_utils');
jest.mock('~/lib/utils/url_utility');
jest.mock('~/flash');
@@ -32,6 +31,10 @@ describe('operation settings external dashboard component', () => {
wrapper = shallow ? shallowMount(...config) : mount(...config);
};
+ beforeEach(() => {
+ jest.spyOn(axios, 'patch').mockImplementation();
+ });
+
afterEach(() => {
if (wrapper.destroy) {
wrapper.destroy();
diff --git a/spec/frontend/projects/projects_filterable_list_spec.js b/spec/frontend/projects/projects_filterable_list_spec.js
new file mode 100644
index 00000000000..e756fb3ab56
--- /dev/null
+++ b/spec/frontend/projects/projects_filterable_list_spec.js
@@ -0,0 +1,31 @@
+import ProjectsFilterableList from '~/projects/projects_filterable_list';
+import { getJSONFixture, setHTMLFixture } from '../helpers/fixtures';
+
+describe('ProjectsFilterableList', () => {
+ let List;
+ let form;
+ let filter;
+ let holder;
+
+ beforeEach(() => {
+ setHTMLFixture(`
+ <form id="project-filter-form">
+ <input name="name" class="js-projects-list-filter" />
+ </div>
+ <div class="js-projects-list-holder"></div>
+ `);
+ getJSONFixture('static/projects.json');
+ form = document.querySelector('form#project-filter-form');
+ filter = document.querySelector('.js-projects-list-filter');
+ holder = document.querySelector('.js-projects-list-holder');
+ List = new ProjectsFilterableList(form, filter, holder);
+ });
+
+ describe('getFilterEndpoint', () => {
+ it('updates converts getPagePath for projects', () => {
+ jest.spyOn(List, 'getPagePath').mockReturnValue('blah/projects?');
+
+ expect(List.getFilterEndpoint()).toEqual('blah/projects.json?');
+ });
+ });
+});
diff --git a/spec/frontend/repository/components/breadcrumbs_spec.js b/spec/frontend/repository/components/breadcrumbs_spec.js
index 068fa317a87..707eae34793 100644
--- a/spec/frontend/repository/components/breadcrumbs_spec.js
+++ b/spec/frontend/repository/components/breadcrumbs_spec.js
@@ -1,12 +1,14 @@
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
+import { GlDropdown } from '@gitlab/ui';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
let vm;
-function factory(currentPath) {
+function factory(currentPath, extraProps = {}) {
vm = shallowMount(Breadcrumbs, {
propsData: {
currentPath,
+ ...extraProps,
},
stubs: {
RouterLink: RouterLinkStub,
@@ -41,4 +43,20 @@ describe('Repository breadcrumbs component', () => {
.attributes('aria-current'),
).toEqual('page');
});
+
+ it('does not render add to tree dropdown when permissions are false', () => {
+ factory('/', { canCollaborate: false });
+
+ vm.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } });
+
+ expect(vm.find(GlDropdown).exists()).toBe(false);
+ });
+
+ it('renders add to tree dropdown when permissions are true', () => {
+ factory('/', { canCollaborate: true });
+
+ vm.setData({ userPermissions: { forkProject: true, createMergeRequestIn: true } });
+
+ expect(vm.find(GlDropdown).exists()).toBe(true);
+ });
});
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index c566057ad3f..e539c560975 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -1,5 +1,5 @@
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
-import { GlBadge } from '@gitlab/ui';
+import { GlBadge, GlLink } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import TableRow from '~/repository/components/table/row.vue';
@@ -142,4 +142,18 @@ describe('Repository table row component', () => {
expect(vm.find(GlBadge).exists()).toBe(true);
});
+
+ it('renders commit and web links with href for submodule', () => {
+ factory({
+ id: '1',
+ path: 'test',
+ type: 'commit',
+ url: 'https://test.com',
+ submoduleTreeUrl: 'https://test.com/commit',
+ currentPath: '/',
+ });
+
+ expect(vm.find('a').attributes('href')).toEqual('https://test.com');
+ expect(vm.find(GlLink).attributes('href')).toEqual('https://test.com/commit');
+ });
});
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index 15cf18700ed..e4d62b044ca 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -2,10 +2,10 @@ import Vue from 'vue';
import * as jqueryMatchers from 'custom-jquery-matchers';
import $ from 'jquery';
import Translate from '~/vue_shared/translate';
-import axios from '~/lib/utils/axios_utils';
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';
// Expose jQuery so specs using jQuery plugins can be imported nicely.
// Here is an issue to explore better alternatives:
@@ -14,6 +14,8 @@ window.jQuery = $;
process.on('unhandledRejection', global.promiseRejectionHandler);
+setupManualMocks();
+
afterEach(() =>
// give Promises a bit more time so they fail the right test
new Promise(setImmediate).then(() => {
@@ -24,18 +26,6 @@ afterEach(() =>
initializeTestTimeout(process.env.CI ? 5000 : 500);
-// fail tests for unmocked requests
-beforeEach(done => {
- axios.defaults.adapter = config => {
- const error = new Error(`Unexpected unmocked request: ${JSON.stringify(config, null, 2)}`);
- error.config = config;
- done.fail(error);
- return Promise.reject(error);
- };
-
- done();
-});
-
Vue.config.devtools = false;
Vue.config.productionTip = false;
@@ -79,3 +69,9 @@ Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => {
// Tech debt issue TBD
testUtilsConfig.logModifiedComponents = false;
+
+// Basic stub for MutationObserver
+global.MutationObserver = () => ({
+ disconnect: () => {},
+ observe: () => {},
+});
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..d69b4c7c162 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
@@ -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/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index 93b86b9b812..dec6b23d72a 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -143,6 +143,26 @@ describe GitlabSchema do
end
end
+ context 'for classes that are not ActiveRecord subclasses and have implemented .lazy_find' do
+ it 'returns the correct record' do
+ note = create(:discussion_note_on_merge_request)
+
+ result = described_class.object_from_id(note.to_global_id)
+
+ expect(result.__sync).to eq(note)
+ end
+
+ it 'batchloads the queries' do
+ note1 = create(:discussion_note_on_merge_request)
+ note2 = create(:discussion_note_on_merge_request)
+
+ expect do
+ [described_class.object_from_id(note1.to_global_id),
+ described_class.object_from_id(note2.to_global_id)].map(&:__sync)
+ end.not_to exceed_query_limit(1)
+ end
+ end
+
context 'for other classes' do
# We cannot use an anonymous class here as `GlobalID` expects `.name` not
# to return `nil`
diff --git a/spec/graphql/types/diff_refs_type_spec.rb b/spec/graphql/types/diff_refs_type_spec.rb
new file mode 100644
index 00000000000..91017c827ad
--- /dev/null
+++ b/spec/graphql/types/diff_refs_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['DiffRefs'] do
+ it { expect(described_class.graphql_name).to eq('DiffRefs') }
+
+ it { expect(described_class).to have_graphql_fields(:base_sha, :head_sha, :start_sha) }
+end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index f73bd062369..59bd0123d88 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -13,7 +13,7 @@ describe GitlabSchema.types['MergeRequest'] do
description_html state created_at updated_at source_project target_project
project project_id source_project_id target_project_id source_branch
target_branch work_in_progress merge_when_pipeline_succeeds diff_head_sha
- merge_commit_sha user_notes_count should_remove_source_branch
+ merge_commit_sha user_notes_count should_remove_source_branch diff_refs
force_remove_source_branch merge_status in_progress_merge_commit_sha
merge_error allow_collaboration should_be_rebased rebase_commit_sha
rebase_in_progress merge_commit_message default_merge_commit_message
diff --git a/spec/graphql/types/notes/diff_position_type_spec.rb b/spec/graphql/types/notes/diff_position_type_spec.rb
index 2f8724d7f0d..345bca8f702 100644
--- a/spec/graphql/types/notes/diff_position_type_spec.rb
+++ b/spec/graphql/types/notes/diff_position_type_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe GitlabSchema.types['DiffPosition'] do
it 'exposes the expected fields' do
- expected_fields = [:head_sha, :base_sha, :start_sha, :file_path, :old_path,
+ expected_fields = [:diff_refs, :file_path, :old_path,
:new_path, :position_type, :old_line, :new_line, :x, :y,
:width, :height]
diff --git a/spec/graphql/types/notes/discussion_type_spec.rb b/spec/graphql/types/notes/discussion_type_spec.rb
index 2a1eb0efd35..ba7fc961212 100644
--- a/spec/graphql/types/notes/discussion_type_spec.rb
+++ b/spec/graphql/types/notes/discussion_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
describe GitlabSchema.types['Discussion'] do
- it { is_expected.to have_graphql_fields(:id, :created_at, :notes) }
+ it { is_expected.to have_graphql_fields(:id, :created_at, :notes, :reply_id) }
it { is_expected.to require_graphql_authorizations(:read_note) }
end
diff --git a/spec/graphql/types/tree/submodule_type_spec.rb b/spec/graphql/types/tree/submodule_type_spec.rb
index bdb3149b41c..768eccba68c 100644
--- a/spec/graphql/types/tree/submodule_type_spec.rb
+++ b/spec/graphql/types/tree/submodule_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::SubmoduleType do
it { expect(described_class.graphql_name).to eq('Submodule') }
- it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path) }
+ it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :tree_url) }
end
diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb
index aa0442ab847..94998d302f9 100644
--- a/spec/helpers/avatars_helper_spec.rb
+++ b/spec/helpers/avatars_helper_spec.rb
@@ -16,7 +16,7 @@ describe AvatarsHelper do
shared_examples 'resource with a custom avatar' do |source_type|
it 'returns a custom avatar image' do
expect(public_send("#{source_type}_icon", *helper_args))
- .to eq "<img src=\"#{resource.avatar.url}\" alt=\"Banana sample\" />"
+ .to eq "<img src=\"#{resource.avatar.url}\" />"
end
end
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
index 6808ed86c9a..1f236429347 100644
--- a/spec/helpers/blob_helper_spec.rb
+++ b/spec/helpers/blob_helper_spec.rb
@@ -29,14 +29,15 @@ describe BlobHelper do
let(:project) { create(:project, :repository, namespace: namespace) }
before do
- allow(self).to receive(:current_user).and_return(nil)
- allow(self).to receive(:can_collaborate_with_project?).and_return(true)
+ allow(helper).to receive(:current_user).and_return(nil)
+ allow(helper).to receive(:can?).and_return(true)
+ allow(helper).to receive(:can_collaborate_with_project?).and_return(true)
end
it 'verifies blob is text' do
expect(helper).not_to receive(:blob_text_viewable?)
- button = edit_blob_button(project, 'refs/heads/master', 'README.md')
+ button = helper.edit_blob_button(project, 'refs/heads/master', 'README.md')
expect(button).to start_with('<button')
end
@@ -46,25 +47,25 @@ describe BlobHelper do
expect(project.repository).not_to receive(:blob_at)
- edit_blob_button(project, 'refs/heads/master', 'README.md', blob: blob)
+ helper.edit_blob_button(project, 'refs/heads/master', 'README.md', blob: blob)
end
it 'returns a link with the proper route' do
stub_feature_flags(web_ide_default: false)
- link = edit_blob_button(project, 'master', 'README.md')
+ link = helper.edit_blob_button(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md")
end
it 'returns a link with a Web IDE route' do
- link = edit_blob_button(project, 'master', 'README.md')
+ link = helper.edit_blob_button(project, 'master', 'README.md')
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/-/ide/project/#{project.full_path}/edit/master/-/README.md")
end
it 'returns a link with the passed link_opts on the expected route' do
stub_feature_flags(web_ide_default: false)
- link = edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 })
+ link = helper.edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 })
expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md?mr_id=10")
end
@@ -203,6 +204,13 @@ describe BlobHelper do
describe '#ide_edit_path' do
let(:project) { create(:project) }
+ let(:current_user) { create(:user) }
+ let(:can_push_code) { true }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(helper).to receive(:can?).and_return(can_push_code)
+ end
around do |example|
old_script_name = Rails.application.routes.default_url_options[:script_name]
@@ -243,5 +251,21 @@ describe BlobHelper do
expect(helper.ide_edit_path(project, "testing/slashes", "readme.md/")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/slashes/-/readme.md/")
end
+
+ context 'when user is not logged in' do
+ let(:current_user) { nil }
+
+ it 'returns IDE path inside the project' do
+ expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master")
+ end
+ end
+
+ context 'when user cannot push to the project' do
+ let(:can_push_code) { false }
+
+ it "returns IDE path with the user's fork" do
+ expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{current_user.namespace.full_path}/#{project.path}/edit/master")
+ end
+ end
end
end
diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb
index b22947911b9..f014537eb54 100644
--- a/spec/helpers/boards_helper_spec.rb
+++ b/spec/helpers/boards_helper_spec.rb
@@ -1,10 +1,12 @@
require 'spec_helper'
describe BoardsHelper do
+ set(:project) { create(:project) }
+
describe '#build_issue_link_base' do
context 'project board' do
it 'returns correct path for project board' do
- @project = create(:project)
+ @project = project
@board = create(:board, project: @project)
expect(build_issue_link_base).to eq("/#{@project.namespace.path}/#{@project.path}/issues")
@@ -31,7 +33,6 @@ describe BoardsHelper do
describe '#board_data' do
let(:user) { create(:user) }
- let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
before do
@@ -46,4 +47,15 @@ describe BoardsHelper do
expect(helper.board_data[:lists_endpoint]).to eq(board_lists_path(board))
end
end
+
+ describe '#current_board_json' do
+ let(:board_json) { helper.current_board_json }
+
+ it 'can serialise with a basic set of attributes' do
+ board = create(:board, project: project)
+ assign(:board, board)
+
+ expect(board_json).to match_schema('current-board')
+ end
+ end
end
diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb
index 023238ee0ae..49e23366355 100644
--- a/spec/helpers/dashboard_helper_spec.rb
+++ b/spec/helpers/dashboard_helper_spec.rb
@@ -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/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb
index e6aacb5b92b..d25f0c6de4a 100644
--- a/spec/helpers/emails_helper_spec.rb
+++ b/spec/helpers/emails_helper_spec.rb
@@ -103,7 +103,7 @@ describe EmailsHelper do
appearance = create :appearance, header_logo: fixture_file_upload('spec/fixtures/dk.png')
expect(header_logo).to eq(
- %{<img style="height: 50px" src="/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png" alt="Dk" />}
+ %{<img style="height: 50px" src="/uploads/-/system/appearance/header_logo/#{appearance.id}/dk.png" />}
)
end
end
diff --git a/spec/helpers/icons_helper_spec.rb b/spec/helpers/icons_helper_spec.rb
index 37e9ddadb8c..f92b94a9583 100644
--- a/spec/helpers/icons_helper_spec.rb
+++ b/spec/helpers/icons_helper_spec.rb
@@ -125,6 +125,14 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'filename.png')).to eq 'file-image-o'
end
+ it 'returns file-image-o class with .apng' do
+ expect(file_type_icon_class('file', 0, 'filename.apng')).to eq 'file-image-o'
+ end
+
+ it 'returns file-image-o class with .webp' do
+ expect(file_type_icon_class('file', 0, 'filename.webp')).to eq 'file-image-o'
+ end
+
it 'returns file-archive-o class with .tar' do
expect(file_type_icon_class('file', 0, 'filename.tar')).to eq 'file-archive-o'
end
@@ -145,6 +153,10 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'filename.MP3')).to eq 'file-audio-o'
end
+ it 'returns file-audio-o class with .m4a' do
+ expect(file_type_icon_class('file', 0, 'filename.m4a')).to eq 'file-audio-o'
+ end
+
it 'returns file-audio-o class with .wav' do
expect(file_type_icon_class('file', 0, 'filename.wav')).to eq 'file-audio-o'
end
@@ -161,6 +173,10 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'filename.mp4')).to eq 'file-video-o'
end
+ it 'returns file-word-o class with .odt' do
+ expect(file_type_icon_class('file', 0, 'filename.odt')).to eq 'file-word-o'
+ end
+
it 'returns file-word-o class with .doc' do
expect(file_type_icon_class('file', 0, 'filename.doc')).to eq 'file-word-o'
end
@@ -185,6 +201,10 @@ describe IconsHelper do
expect(file_type_icon_class('file', 0, 'filename.xlsx')).to eq 'file-excel-o'
end
+ it 'returns file-excel-o class with .odp' do
+ expect(file_type_icon_class('file', 0, 'filename.odp')).to eq 'file-powerpoint-o'
+ end
+
it 'returns file-excel-o class with .ppt' do
expect(file_type_icon_class('file', 0, 'filename.ppt')).to eq 'file-powerpoint-o'
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 1d1446eaa30..3c8179460ac 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -202,5 +202,46 @@ describe IssuablesHelper do
}
expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data))
end
+
+ describe '#zoomMeetingUrl in issue' do
+ let(:issue) { create(:issue, author: user, description: description) }
+
+ before do
+ assign(:project, issue.project)
+ end
+
+ context 'no zoom links in the issue description' do
+ let(:description) { 'issue text' }
+
+ it 'does not set zoomMeetingUrl' do
+ expect(helper.issuable_initial_data(issue))
+ .not_to include(:zoomMeetingUrl)
+ end
+ end
+
+ context 'no zoom links in the issue description if it has link but not a zoom link' do
+ let(:description) { 'issue text https://stackoverflow.com/questions/22' }
+
+ it 'does not set zoomMeetingUrl' do
+ expect(helper.issuable_initial_data(issue))
+ .not_to include(:zoomMeetingUrl)
+ end
+ end
+
+ context 'with two zoom links in description' do
+ let(:description) do
+ <<~TEXT
+ issue text and
+ zoom call on https://zoom.us/j/123456789 this url
+ and new zoom url https://zoom.us/s/lastone and some more text
+ TEXT
+ end
+
+ it 'sets zoomMeetingUrl value to the last url' do
+ expect(helper.issuable_initial_data(issue))
+ .to include(zoomMeetingUrl: 'https://zoom.us/s/lastone')
+ end
+ end
+ end
end
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 6c6410cee9b..f6e1720e113 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -268,7 +268,7 @@ describe MarkupHelper do
end
end
- describe 'markup' do
+ describe '#markup' do
let(:content) { 'Noël' }
it 'preserves encoding' do
@@ -302,6 +302,77 @@ describe MarkupHelper do
end
end
+ describe '#markup_unsafe' do
+ subject { helper.markup_unsafe(file_name, text, context) }
+
+ let(:file_name) { 'foo.bar' }
+ let(:text) { 'Noël' }
+ let(:project_base) { build(:project, :repository) }
+ let(:context) { { project: project_base } }
+
+ context 'when text is missing' do
+ let(:text) { nil }
+
+ it 'returns an empty string' do
+ is_expected.to eq('')
+ end
+ end
+
+ context 'when file is a markdown file' do
+ let(:file_name) { 'foo.md' }
+
+ it 'returns html (rendered by Banzai)' do
+ expected_html = '<p data-sourcepos="1:1-1:5" dir="auto">Noël</p>'
+
+ expect(Banzai).to receive(:render).with(text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+
+ context 'when renderer returns an error' do
+ before do
+ allow(Banzai).to receive(:render).and_raise("An error")
+ end
+
+ it 'returns html (rendered by ActionView:TextHelper)' do
+ is_expected.to eq('<p>Noël</p>')
+ end
+ end
+ end
+
+ context 'when file is asciidoc file' do
+ let(:file_name) { 'foo.adoc' }
+
+ it 'returns html (rendered by Gitlab::Asciidoc)' do
+ expected_html = "<div>\n<p>Noël</p>\n</div>"
+
+ expect(Gitlab::Asciidoc).to receive(:render).with(text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+ end
+
+ context 'when file is a regular text file' do
+ let(:file_name) { 'foo.txt' }
+
+ it 'returns html (rendered by ActionView::TagHelper)' do
+ is_expected.to eq('<pre class="plain-readme">Noël</pre>')
+ end
+ end
+
+ context 'when file has an unknown type' do
+ let(:file_name) { 'foo' }
+
+ it 'returns html (rendered by Gitlab::OtherMarkup)' do
+ expected_html = 'Noël'
+
+ expect(Gitlab::OtherMarkup).to receive(:render).with(file_name, text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+ end
+ end
+
describe '#first_line_in_markdown' do
shared_examples_for 'common markdown examples' do
let(:project_base) { build(:project, :repository) }
diff --git a/spec/helpers/sorting_helper_spec.rb b/spec/helpers/sorting_helper_spec.rb
index f405268d198..5397a47b3dd 100644
--- a/spec/helpers/sorting_helper_spec.rb
+++ b/spec/helpers/sorting_helper_spec.rb
@@ -4,6 +4,11 @@ require 'spec_helper'
describe SortingHelper do
include ApplicationHelper
include IconsHelper
+ include ExploreHelper
+
+ def set_sorting_url(option)
+ allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: option }))
+ end
describe '#issuable_sort_option_title' do
it 'returns correct title for issuable_sort_option_overrides key' do
@@ -21,7 +26,7 @@ describe SortingHelper do
describe '#issuable_sort_direction_button' do
before do
- allow(self).to receive(:request).and_return(double(path: 'http://test.com', query_parameters: { label_name: 'test_label' }))
+ set_sorting_url 'test_label'
end
it 'keeps label filter param' do
@@ -44,4 +49,145 @@ describe SortingHelper do
expect(issuable_sort_direction_button('due_date')).to include('sort-lowest')
end
end
+
+ def stub_controller_path(value)
+ allow(helper.controller).to receive(:controller_path).and_return(value)
+ end
+
+ def project_common_options
+ {
+ sort_value_latest_activity => sort_title_latest_activity,
+ sort_value_recently_created => sort_title_created_date,
+ sort_value_name => sort_title_name,
+ sort_value_stars_desc => sort_title_stars
+ }
+ end
+
+ describe 'with `admin/projects` controller' do
+ before do
+ stub_controller_path 'admin/projects'
+ end
+
+ describe '#projects_sort_options_hash' do
+ it 'returns a hash of available sorting options' do
+ admin_options = project_common_options.merge({
+ sort_value_oldest_activity => sort_title_oldest_activity,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_stars_desc => sort_title_most_stars,
+ sort_value_largest_repo => sort_title_largest_repo
+ })
+
+ expect(projects_sort_options_hash).to eq(admin_options)
+ end
+ end
+ end
+
+ describe 'with `projects` controller' do
+ before do
+ stub_controller_path 'projects'
+ end
+
+ describe '#projects_sort_options_hash' do
+ it 'returns a hash of available sorting options' do
+ expect(projects_sort_options_hash).to include(project_common_options)
+ end
+ end
+
+ describe '#projects_reverse_sort_options_hash' do
+ context 'returns a reversed hash of available sorting options' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:sort_key, :reverse_sort_title) do
+ sort_value_latest_activity | sort_value_oldest_activity
+ sort_value_recently_created | sort_value_oldest_created
+ sort_value_name | sort_value_name_desc
+ sort_value_stars_desc | sort_value_stars_asc
+ sort_value_oldest_activity | sort_value_latest_activity
+ sort_value_oldest_created | sort_value_recently_created
+ sort_value_name_desc | sort_value_name
+ sort_value_stars_asc | sort_value_stars_desc
+ end
+
+ with_them do
+ it do
+ reverse_hash = projects_reverse_sort_options_hash
+
+ expect(reverse_hash).to include(sort_key)
+ expect(reverse_hash[sort_key]).to eq(reverse_sort_title)
+ end
+ end
+ end
+ end
+
+ describe '#project_sort_direction_button' do
+ context 'returns the correct icon for each sort option' do
+ using RSpec::Parameterized::TableSyntax
+
+ sort_lowest_icon = 'sort-lowest'
+ sort_highest_icon = 'sort-highest'
+
+ where(:selected_sort, :icon) do
+ sort_value_latest_activity | sort_highest_icon
+ sort_value_recently_created | sort_highest_icon
+ sort_value_name_desc | sort_highest_icon
+ sort_value_stars_desc | sort_highest_icon
+ sort_value_oldest_activity | sort_lowest_icon
+ sort_value_oldest_created | sort_lowest_icon
+ sort_value_name | sort_lowest_icon
+ sort_value_stars_asc | sort_lowest_icon
+ end
+
+ with_them do
+ it do
+ set_sorting_url selected_sort
+
+ expect(project_sort_direction_button(selected_sort)).to include(icon)
+ end
+ end
+ end
+
+ it 'returns the correct link to reverse the current sort option' do
+ sort_options_links = projects_reverse_sort_options_hash
+
+ sort_options_links.each do |selected_sort, reverse_sort|
+ set_sorting_url selected_sort
+
+ expect(project_sort_direction_button(selected_sort)).to include(reverse_sort)
+ end
+ end
+ end
+
+ describe '#projects_sort_option_titles' do
+ it 'returns a hash of titles for the sorting options' do
+ options = project_common_options.merge({
+ sort_value_oldest_activity => sort_title_latest_activity,
+ sort_value_oldest_created => sort_title_created_date,
+ sort_value_name_desc => sort_title_name,
+ sort_value_stars_asc => sort_title_stars
+ })
+
+ expect(projects_sort_option_titles).to eq(options)
+ end
+ end
+
+ describe 'with project_list_filter_bar off' do
+ before do
+ stub_feature_flags(project_list_filter_bar: false)
+ end
+
+ describe '#projects_sort_options_hash' do
+ it 'returns a hash of available sorting options' do
+ options = project_common_options.merge({
+ sort_value_oldest_activity => sort_title_oldest_activity,
+ sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_recently_created => sort_title_recently_created,
+ sort_value_stars_desc => sort_title_most_stars
+ })
+
+ expect(projects_sort_options_hash).to eq(options)
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 62c00964524..4bd0fbb76ca 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -31,7 +31,7 @@ describe StorageHelper do
build_artifacts_size: 30.megabytes))
end
- let(:message) { '10 KB repositories, 10 Bytes wikis, 30 MB build artifacts, 20 GB LFS' }
+ let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index ea48c69e0ae..b922b910c89 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
@@ -238,6 +231,22 @@ describe SubmoduleHelper do
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)
allow(repo).to receive(:submodule_url_for).and_return(url)
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 25a2fcf5a81..2d276696208 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -137,32 +137,6 @@ describe VisibilityLevelHelper do
end
end
- describe "disallowed_visibility_level_description" do
- let(:group) { create(:group, :internal) }
- let!(:subgroup) { create(:group, :internal, parent: group) }
- let!(:project) { create(:project, :internal, group: group) }
-
- describe "project" do
- it "provides correct description for disabled levels" do
- expect(disallowed_visibility_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
- expect(strip_tags disallowed_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC, project))
- .to include "the visibility of #{project.group.name} is internal"
- end
- end
-
- describe "group" do
- it "provides correct description for disabled levels" do
- expect(disallowed_visibility_level?(group, Gitlab::VisibilityLevel::PRIVATE)).to be_truthy
- expect(disallowed_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE, group))
- .to include "it contains projects with higher visibility", "it contains sub-groups with higher visibility"
-
- expect(disallowed_visibility_level?(subgroup, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
- expect(strip_tags disallowed_visibility_level_description(Gitlab::VisibilityLevel::PUBLIC, subgroup))
- .to include "the visibility of #{group.name} is internal"
- end
- end
- end
-
describe "selected_visibility_level" do
let(:group) { create(:group, :public) }
let!(:project) { create(:project, :internal, group: group) }
@@ -207,4 +181,50 @@ describe VisibilityLevelHelper do
end
end
end
+
+ describe 'multiple_visibility_levels_restricted?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:user) { create(:user) }
+
+ subject { helper.multiple_visibility_levels_restricted? }
+
+ where(:restricted_visibility_levels, :expected) do
+ [Gitlab::VisibilityLevel::PUBLIC] | false
+ [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL] | true
+ [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PRIVATE] | true
+ end
+
+ with_them do
+ before do
+ allow(helper).to receive(:current_user) { user }
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:restricted_visibility_levels) { restricted_visibility_levels }
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+ end
+
+ describe 'all_visibility_levels_restricted?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:user) { create(:user) }
+
+ subject { helper.all_visibility_levels_restricted? }
+
+ where(:restricted_visibility_levels, :expected) do
+ [Gitlab::VisibilityLevel::PUBLIC] | false
+ [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL] | false
+ Gitlab::VisibilityLevel.values | true
+ end
+
+ with_them do
+ before do
+ allow(helper).to receive(:current_user) { user }
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:restricted_visibility_levels) { restricted_visibility_levels }
+ end
+
+ it { is_expected.to eq(expected) }
+ end
+ end
end
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/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 5266b1bdbfc..36bd7ada4f0 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -365,4 +365,17 @@ describe('Store', () => {
expect(boardsStore.timeTracking.limitToHours).toEqual(true);
});
});
+
+ describe('setCurrentBoard', () => {
+ const dummyBoard = 'hoverboard';
+
+ it('sets the current board', () => {
+ const { state } = boardsStore;
+ state.currentBoard = null;
+
+ boardsStore.setCurrentBoard(dummyBoard);
+
+ expect(state.currentBoard).toEqual(dummyBoard);
+ });
+ });
});
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..504bc51778c
--- /dev/null
+++ b/spec/javascripts/boards/components/boards_selector_spec.js
@@ -0,0 +1,205 @@
+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'),
+ );
+
+ vm.$el.querySelector('.js-dropdown-toggle').click();
+
+ 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/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/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/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
index 121e99c9783..68326e37ae7 100644
--- a/spec/javascripts/helpers/vue_test_utils_helper.js
+++ b/spec/javascripts/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/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js
index 88652202a8e..c5de31a4138 100644
--- a/spec/javascripts/helpers/vuex_action_helper.js
+++ b/spec/javascripts/helpers/vuex_action_helper.js
@@ -89,9 +89,7 @@ export default (
payload,
);
- return new Promise(resolve => {
- setImmediate(resolve);
- })
+ return new Promise(setImmediate)
.then(() => result)
.catch(error => {
validateResults();
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/issuable_spec.js b/spec/javascripts/issuable_spec.js
index 25543053eba..4d57bfb1b33 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -2,14 +2,14 @@ import $ from 'jquery';
import MockAdaptor from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
+import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
describe('Issuable', () => {
- let Issuable;
describe('initBulkUpdate', () => {
it('should not set bulkUpdateSidebar', () => {
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
- expect(Issuable.bulkUpdateSidebar).not.toBeDefined();
+ expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeNull();
});
it('should set bulkUpdateSidebar', () => {
@@ -17,9 +17,9 @@ describe('Issuable', () => {
element.classList.add('issues-bulk-update');
document.body.appendChild(element);
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
- expect(Issuable.bulkUpdateSidebar).toBeDefined();
+ expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeDefined();
});
});
@@ -36,7 +36,7 @@ describe('Issuable', () => {
input.setAttribute('id', 'issuable_email');
document.body.appendChild(input);
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
mock = new MockAdaptor(axios);
diff --git a/spec/javascripts/monitoring/charts/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/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js
index 12b73002f97..127a4a7955a 100644
--- a/spec/javascripts/monitoring/charts/single_stat_spec.js
+++ b/spec/javascripts/monitoring/charts/single_stat_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
+import { graphDataPrometheusQuery } from '../mock_data';
describe('Single Stat Chart component', () => {
let singleStatChart;
@@ -7,9 +8,7 @@ describe('Single Stat Chart component', () => {
beforeEach(() => {
singleStatChart = shallowMount(SingleStatChart, {
propsData: {
- title: 'Time to render',
- value: 1,
- unit: 'sec',
+ graphData: graphDataPrometheusQuery,
},
});
});
@@ -19,9 +18,9 @@ describe('Single Stat Chart component', () => {
});
describe('computed', () => {
- describe('valueWithUnit', () => {
+ describe('engineeringNotation', () => {
it('should interpolate the value and unit props', () => {
- expect(singleStatChart.vm.valueWithUnit).toBe('1sec');
+ expect(singleStatChart.vm.engineeringNotation).toBe('91MB');
});
});
});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 7bbb215475a..85e660d3925 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -935,3 +935,75 @@ export const dashboardGitResponse = [
default: false,
},
];
+
+export const graphDataPrometheusQuery = {
+ title: 'Super Chart A2',
+ type: 'single-stat',
+ weight: 2,
+ metrics: [
+ {
+ id: 'metric_a1',
+ metric_id: 2,
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ },
+ ],
+ queries: [
+ {
+ metricId: null,
+ id: 'metric_a1',
+ metric_id: 2,
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ result: [
+ {
+ metric: { job: 'prometheus' },
+ value: ['2019-06-26T21:03:20.881Z', 91],
+ },
+ ],
+ },
+ ],
+};
+
+export const graphDataPrometheusQueryRange = {
+ title: 'Super Chart A1',
+ type: 'area',
+ weight: 2,
+ metrics: [
+ {
+ id: 'metric_a1',
+ metric_id: 2,
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ },
+ ],
+ queries: [
+ {
+ metricId: null,
+ id: 'metric_a1',
+ metric_id: 2,
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ result: [
+ {
+ metric: {},
+ values: [[1495700554.925, '8.0390625'], [1495700614.925, '8.0390625']],
+ },
+ ],
+ },
+ ],
+};
diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js
new file mode 100644
index 00000000000..8ce24041e97
--- /dev/null
+++ b/spec/javascripts/monitoring/panel_type_spec.js
@@ -0,0 +1,44 @@
+import { shallowMount } from '@vue/test-utils';
+import PanelType from '~/monitoring/components/panel_type.vue';
+import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+import { graphDataPrometheusQueryRange } from './mock_data';
+
+describe('Panel Type component', () => {
+ let panelType;
+ const dashboardWidth = 100;
+
+ describe('When no graphData is available', () => {
+ let glEmptyChart;
+ const graphDataNoResult = graphDataPrometheusQueryRange;
+ graphDataNoResult.queries[0].result = [];
+
+ beforeEach(() => {
+ panelType = shallowMount(PanelType, {
+ propsData: {
+ 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);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js
index 91580366531..43776b1b7f2 100644
--- a/spec/javascripts/monitoring/store/mutations_spec.js
+++ b/spec/javascripts/monitoring/store/mutations_spec.js
@@ -115,12 +115,14 @@ describe('Monitoring mutations', () => {
environmentsEndpoint: 'environments.json',
deploymentsEndpoint: 'deployments.json',
dashboardEndpoint: 'dashboard.json',
+ projectPath: '/gitlab-org/gitlab-ce',
});
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
expect(stateCopy.environmentsEndpoint).toEqual('environments.json');
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json');
+ expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-ce');
});
});
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index e3c455d1686..5570d57b8b2 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -1,5 +1,6 @@
-import { getTimeDiff } from '~/monitoring/utils';
+import { getTimeDiff, graphDataValidatorForValues } from '~/monitoring/utils';
import { timeWindows } from '~/monitoring/constants';
+import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
describe('getTimeDiff', () => {
it('defaults to an 8 hour (28800s) difference', () => {
@@ -27,3 +28,27 @@ describe('getTimeDiff', () => {
});
});
});
+
+describe('graphDataValidatorForValues', () => {
+ /*
+ * When dealing with a metric using the query format, e.g.
+ * query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024'
+ * the validator will look for the `value` key instead of `values`
+ */
+ it('validates data with the query format', () => {
+ const validGraphData = graphDataValidatorForValues(true, graphDataPrometheusQuery);
+
+ expect(validGraphData).toBe(true);
+ });
+
+ /*
+ * When dealing with a metric using the query?range format, e.g.
+ * query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ * the validator will look for the `values` key instead of `value`
+ */
+ it('validates data with the query_range format', () => {
+ const validGraphData = graphDataValidatorForValues(false, graphDataPrometheusQueryRange);
+
+ expect(validGraphData).toBe(true);
+ });
+});
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 362963ddaf4..88c86746992 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -251,6 +251,21 @@ describe('issue_comment_form component', () => {
});
});
});
+
+ describe('when toggling state', () => {
+ it('should update MR count', done => {
+ spyOn(vm, 'closeIssue').and.returnValue(Promise.resolve());
+
+ const updateMrCountSpy = spyOnDependency(CommentForm, 'refreshUserMergeRequestCounts');
+ vm.toggleIssueState();
+
+ Vue.nextTick(() => {
+ expect(updateMrCountSpy).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
});
describe('issue is confidential', () => {
diff --git a/spec/javascripts/notes/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/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index c97ff5236ec..c461c28a37b 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -18,6 +18,8 @@ import {
noteableDataMock,
individualNote,
} from '../mock_data';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
const TEST_ERROR_MESSAGE = 'Test error message';
@@ -335,28 +337,24 @@ describe('Actions Notes Store', () => {
});
describe('deleteNote', () => {
- const interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify({}), {
- status: 200,
- }),
- );
- };
+ const endpoint = `${TEST_HOST}/note`;
+ let axiosMock;
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
+ axiosMock = new AxiosMockAdapter(axios);
+ axiosMock.onDelete(endpoint).replyOnce(200, {});
$('body').attr('data-page', '');
});
afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ axiosMock.restore();
$('body').attr('data-page', '');
});
it('commits DELETE_NOTE and dispatches updateMergeRequestWidget', done => {
- const note = { path: `${gl.TEST_HOST}`, id: 1 };
+ const note = { path: endpoint, id: 1 };
testAction(
actions.deleteNote,
@@ -381,7 +379,7 @@ describe('Actions Notes Store', () => {
});
it('dispatches removeDiscussionsFromDiff on merge request page', done => {
- const note = { path: `${gl.TEST_HOST}`, id: 1 };
+ const note = { path: endpoint, id: 1 };
$('body').attr('data-page', 'projects:merge_requests:show');
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 87237d2853d..7b9b8d2b039 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -90,7 +90,7 @@ describe('Registry List', () => {
.textContent.trim()
.replace(/[\r\n]+/g, ' '),
).toEqual(
- 'With the Container Registry, every project can have its own space to store its Docker images. Learn more about the Container Registry.',
+ 'With the Container Registry, every project can have its own space to store its Docker images. More Information',
);
done();
}, 0);
diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js
index 55017b3e26b..2a5d8dd11da 100644
--- a/spec/javascripts/registry/components/collapsible_container_spec.js
+++ b/spec/javascripts/registry/components/collapsible_container_spec.js
@@ -77,7 +77,7 @@ describe('collapsible registry container', () => {
spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
Vue.nextTick(() => {
- document.querySelector('#confirm-repo-deletion-modal .btn-danger').click();
+ document.querySelector(`#${vm.modalId} .btn-danger`).click();
expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo);
done();
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
index 6a0b16f592e..31ac970378e 100644
--- a/spec/javascripts/registry/components/table_registry_spec.js
+++ b/spec/javascripts/registry/components/table_registry_spec.js
@@ -51,7 +51,7 @@ describe('table registry', () => {
spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
Vue.nextTick(() => {
- document.querySelector('#confirm-image-deletion-modal .btn-danger').click();
+ document.querySelector(`#${vm.modalId} .btn-danger`).click();
expect(vm.deleteItem).toHaveBeenCalledWith(firstImage);
expect(vm.itemToBeDeleted).toBeNull();
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 50741e249ca..ce453d7c483 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -3,6 +3,7 @@
*/
import $ from 'jquery';
+import 'core-js/features/set-immediate';
import 'vendor/jasmine-jquery';
import '~/commons';
import Vue from 'vue';
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/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index bb76616be56..ba3ba01944d 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -58,9 +58,11 @@ const createComponent = (customConfig = {}) => {
describe('ReadyToMerge', () => {
let vm;
+ let updateMrCountSpy;
beforeEach(() => {
vm = createComponent();
+ updateMrCountSpy = spyOnDependency(ReadyToMerge, 'refreshUserMergeRequestCounts');
});
afterEach(() => {
@@ -461,6 +463,7 @@ describe('ReadyToMerge', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(eventHub.$emit).toHaveBeenCalledWith('FetchActionsContent');
expect(vm.initiateRemoveSourceBranchPolling).toHaveBeenCalled();
+ expect(updateMrCountSpy).toHaveBeenCalled();
expect(cpc).toBeFalsy();
expect(spc).toBeTruthy();
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index c788da55cd2..b0a00392957 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -114,7 +114,7 @@ describe API::Helpers::Pagination do
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.size).to eq(1)
expect(paginated_relation.order_values.first).to be_descending
- expect(paginated_relation.order_values.first.expr.name).to eq :id
+ expect(paginated_relation.order_values.first.expr.name).to eq 'id'
end
end
@@ -151,9 +151,9 @@ describe API::Helpers::Pagination do
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.size).to eq(2)
expect(paginated_relation.order_values.first).to be_descending
- expect(paginated_relation.order_values.first.expr.name).to eq :name
+ expect(paginated_relation.order_values.first.expr.name).to eq 'name'
expect(paginated_relation.order_values.second).to be_descending
- expect(paginated_relation.order_values.second.expr.name).to eq :id
+ expect(paginated_relation.order_values.second.expr.name).to eq 'id'
end
it 'returns the right records (first page)' do
@@ -341,7 +341,7 @@ describe API::Helpers::Pagination do
expect(resource.order_values).to be_empty
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.first).to be_ascending
- expect(paginated_relation.order_values.first.expr.name).to eq :id
+ expect(paginated_relation.order_values.first.expr.name).to eq 'id'
end
it 'is present it does not add anything' do
@@ -349,7 +349,7 @@ describe API::Helpers::Pagination do
expect(paginated_relation.order_values).to be_present
expect(paginated_relation.order_values.first).to be_descending
- expect(paginated_relation.order_values.first.expr.name).to eq :created_at
+ expect(paginated_relation.order_values.first.expr.name).to eq 'created_at'
end
end
end
diff --git a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
new file mode 100644
index 00000000000..772c94e3180
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::InlineMetricsFilter do
+ include FilterSpecHelper
+
+ let(:input) { %(<a href="#{url}">example</a>) }
+ let(:doc) { filter(input) }
+
+ context 'when the document has an external link' do
+ let(:url) { 'https://foo.com' }
+
+ it 'leaves regular non-metrics links unchanged' do
+ expect(doc.to_s).to eq input
+ end
+ end
+
+ context 'when the document has a metrics dashboard link' do
+ let(:params) { ['foo', 'bar', 12] }
+ let(:url) { urls.metrics_namespace_project_environment_url(*params) }
+
+ it 'leaves the original link unchanged' do
+ expect(doc.at_css('a').to_s).to eq input
+ end
+
+ it 'appends a metrics charts placeholder with dashboard url after metrics links' do
+ node = doc.at_css('.js-render-metrics')
+ expect(node).to be_present
+
+ dashboard_url = urls.metrics_dashboard_namespace_project_environment_url(*params, embedded: true)
+ expect(node.attribute('data-dashboard-url').to_s).to eq dashboard_url
+ end
+
+ context 'when the metrics dashboard link is part of a paragraph' do
+ let(:paragraph) { %(This is an <a href="#{url}">example</a> of metrics.) }
+ let(:input) { %(<p>#{paragraph}</p>) }
+
+ it 'appends the charts placeholder after the enclosing paragraph' do
+ expect(doc.at_css('p').to_s).to include paragraph
+ expect(doc.at_css('.js-render-metrics')).to be_present
+ end
+
+ context 'when the feature is disabled' do
+ before do
+ stub_feature_flags(gfm_embedded_metrics: false)
+ end
+
+ it 'does nothing' do
+ expect(doc.to_s).to eq input
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
new file mode 100644
index 00000000000..fb2186e9d12
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::InlineMetricsRedactorFilter do
+ include FilterSpecHelper
+
+ set(:project) { create(:project) }
+
+ let(:url) { urls.metrics_dashboard_project_environment_url(project, 1, embedded: true) }
+ let(:input) { %(<a href="#{url}">example</a>) }
+ let(:doc) { filter(input) }
+
+ context 'when the feature is disabled' do
+ before do
+ stub_feature_flags(gfm_embedded_metrics: false)
+ end
+
+ it 'does nothing' do
+ expect(doc.to_s).to eq input
+ end
+ end
+
+ context 'without a metrics charts placeholder' do
+ it 'leaves regular non-metrics links unchanged' do
+ expect(doc.to_s).to eq input
+ end
+ end
+
+ context 'with a metrics charts placeholder' do
+ let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) }
+
+ context 'no user is logged in' do
+ it 'redacts the placeholder' do
+ expect(doc.to_s).to be_empty
+ end
+ end
+
+ context 'the user does not have permission do see charts' do
+ let(:doc) { filter(input, current_user: build(:user)) }
+
+ it 'redacts the placeholder' do
+ expect(doc.to_s).to be_empty
+ end
+ end
+
+ context 'the user has requisite permissions' do
+ let(:user) { create(:user) }
+ let(:doc) { filter(input, current_user: user) }
+
+ it 'leaves the placeholder' do
+ project.add_maintainer(user)
+
+ expect(doc.to_s).to eq input
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
index 919825a6102..e87440895e0 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::RedactorFilter do
+describe Banzai::Filter::ReferenceRedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 7b855251a74..e3e6e22568c 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -22,8 +22,8 @@ describe Banzai::ObjectRenderer do
expect(object.user_visible_reference_count).to eq 0
end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::ReferenceRedactor to perform redaction' do
+ expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original
renderer.render([object], :note)
end
@@ -82,8 +82,8 @@ describe Banzai::ObjectRenderer do
expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::ReferenceRedactor to perform redaction' do
+ expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original
renderer.render([cacheless_thing], :title)
end
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 7119c826bca..469692f7b5a 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -32,7 +32,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'parses cross-project references to regular issues' do
@@ -61,7 +61,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'allows to use long external reference syntax for Redmine' do
@@ -70,7 +70,7 @@ describe Banzai::Pipeline::GfmPipeline do
result = described_class.call(markdown, project: project)[:output]
link = result.css('a').first
- expect(link['href']).to eq 'http://redmine/projects/project_name_in_redmine/issues/12'
+ expect(link['href']).to eq 'http://issue-tracker.example.com/issues/12'
end
it 'parses cross-project references to regular issues' do
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index 718649e0e10..a3b47c4d826 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Redactor do
+describe Banzai::ReferenceRedactor do
let(:user) { create(:user) }
let(:project) { build(:project) }
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index aa828e2f0e9..a099f7482c1 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -19,6 +19,24 @@ describe Banzai::Renderer do
object
end
+ describe '#cache_collection_render' do
+ let(:merge_request) { fake_object(fresh: true) }
+ let(:context) { { cache_key: [merge_request, 'field'], rendered: merge_request.field_html } }
+
+ context 'when an item has a rendered field' do
+ before do
+ allow(merge_request).to receive(:field).and_return('This is the field')
+ allow(merge_request).to receive(:field_html).and_return('This is the field')
+ end
+
+ it 'does not touch redis if the field is in the cache' do
+ expect(Rails).not_to receive(:cache)
+
+ described_class.cache_collection_render([{ text: merge_request.field, context: context }])
+ end
+ end
+ end
+
describe '#render_field' do
let(:renderer) { described_class }
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
index b9ffe895bf0..800fa5a6ad6 100644
--- a/spec/lib/forever_spec.rb
+++ b/spec/lib/forever_spec.rb
@@ -4,19 +4,9 @@ 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/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 8f2434acd26..cbd4a509a55 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -45,28 +45,236 @@ module Gitlab
end
context "XSS" do
- links = {
- 'links' => {
+ items = {
+ 'link with extra attribute' => {
input: 'link:mylink"onmouseover="alert(1)[Click Here]',
output: "<div>\n<p><a href=\"mylink\">Click Here</a></p>\n</div>"
},
- 'images' => {
+ 'link with unsafe scheme' => {
+ input: 'link:data://danger[Click Here]',
+ output: "<div>\n<p><a>Click Here</a></p>\n</div>"
+ },
+ 'image with onerror' => {
input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]',
output: "<div>\n<p><span><img src=\"https://localhost.com/image.png\" alt='Alt text\" onerror=\"alert(7)'></span></p>\n</div>"
},
- 'pre' => {
+ 'fenced code with inline script' => {
input: '```mypre"><script>alert(3)</script>',
output: "<div>\n<div>\n<pre class=\"code highlight js-syntax-highlight plaintext\" lang=\"plaintext\" v-pre=\"true\"><code><span id=\"LC1\" class=\"line\" lang=\"plaintext\">\"&gt;</span></code></pre>\n</div>\n</div>"
}
}
- links.each do |name, data|
+ items.each do |name, data|
it "does not convert dangerous #{name} into HTML" do
expect(render(data[:input], context)).to include(data[:output])
end
end
end
+ context 'with admonition' do
+ it 'preserves classes' do
+ input = <<~ADOC
+ NOTE: An admonition paragraph, like this note, grabs the reader’s attention.
+ ADOC
+
+ output = <<~HTML
+ <div class="admonitionblock">
+ <table>
+ <tr>
+ <td class="icon">
+ <i class="fa icon-note" title="Note"></i>
+ </td>
+ <td>
+ An admonition paragraph, like this note, grabs the reader’s attention.
+ </td>
+ </tr>
+ </table>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with passthrough' do
+ it 'removes non heading ids' do
+ input = <<~ADOC
+ ++++
+ <h2 id="foo">Title</h2>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <h2>Title</h2>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+
+ it 'removes non footnote def ids' do
+ input = <<~ADOC
+ ++++
+ <div id="def">Footnote definition</div>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <div>Footnote definition</div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+
+ it 'removes non footnote ref ids' do
+ input = <<~ADOC
+ ++++
+ <a id="ref">Footnote reference</a>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <a>Footnote reference</a>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with footnotes' do
+ it 'preserves ids and links' do
+ input = <<~ADOC
+ This paragraph has a footnote.footnote:[This is the text of the footnote.]
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>
+ </div>
+ <div>
+ <hr>
+ <div id="_footnotedef_1">
+ <a href="#_footnoteref_1">1</a>. This is the text of the footnote.
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with section anchors' do
+ it 'preserves ids and links' do
+ input = <<~ADOC
+ = Title
+
+ == First section
+
+ This is the first section.
+
+ == Second section
+
+ This is the second section.
+
+ == Thunder ⚡ !
+
+ This is the third section.
+ ADOC
+
+ output = <<~HTML
+ <h1>Title</h1>
+ <div>
+ <h2 id="user-content-first-section">
+ <a class="anchor" href="#user-content-first-section"></a>First section</h2>
+ <div>
+ <div>
+ <p>This is the first section.</p>
+ </div>
+ </div>
+ </div>
+ <div>
+ <h2 id="user-content-second-section">
+ <a class="anchor" href="#user-content-second-section"></a>Second section</h2>
+ <div>
+ <div>
+ <p>This is the second section.</p>
+ </div>
+ </div>
+ </div>
+ <div>
+ <h2 id="user-content-thunder">
+ <a class="anchor" href="#user-content-thunder"></a>Thunder ⚡ !</h2>
+ <div>
+ <div>
+ <p>This is the third section.</p>
+ </div>
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with checklist' do
+ it 'preserves classes' do
+ input = <<~ADOC
+ * [x] checked
+ * [ ] not checked
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <ul class="checklist">
+ <li>
+ <p><i class="fa fa-check-square-o"></i> checked</p>
+ </li>
+ <li>
+ <p><i class="fa fa-square-o"></i> not checked</p>
+ </li>
+ </ul>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with marks' do
+ it 'preserves classes' do
+ input = <<~ADOC
+ Werewolves are allergic to #cassia cinnamon#.
+
+ Did the werewolves read the [.small]#small print#?
+
+ Where did all the [.underline.small]#cores# run off to?
+
+ We need [.line-through]#ten# make that twenty VMs.
+
+ [.big]##O##nce upon an infinite loop.
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <p>Werewolves are allergic to <mark>cassia cinnamon</mark>.</p>
+ </div>
+ <div>
+ <p>Did the werewolves read the <span class="small">small print</span>?</p>
+ </div>
+ <div>
+ <p>Where did all the <span class="underline small">cores</span> run off to?</p>
+ </div>
+ <div>
+ <p>We need <span class="line-through">ten</span> make that twenty VMs.</p>
+ </div>
+ <div>
+ <p><span class="big">O</span>nce upon an infinite loop.</p>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
context 'with fenced block' do
it 'highlights syntax' do
input = <<~ADOC
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 1e2aebdc84b..4751f880cee 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -138,6 +138,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 d9c73cff01e..0403830f700 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -297,6 +297,70 @@ describe Gitlab::Auth do
let(:project) { create(:project) }
let(:auth_failure) { Gitlab::Auth::Result.new(nil, nil) }
+ context 'when deploy token and user have the same username' do
+ let(:username) { 'normal_user' }
+ let(:user) { create(:user, username: username, password: 'my-secret') }
+ let(:deploy_token) { create(:deploy_token, username: username, read_registry: false, projects: [project]) }
+
+ before do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: username)
+ end
+
+ it 'succeeds for the token' do
+ auth_success = Gitlab::Auth::Result.new(deploy_token, project, :deploy_token, [:download_code])
+
+ expect(gl_auth.find_for_git_client(username, deploy_token.token, project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
+ it 'succeeds for the user' do
+ auth_success = Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
+
+ expect(gl_auth.find_for_git_client(username, 'my-secret', project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+ end
+
+ context 'when deploy tokens have the same username' do
+ context 'and belong to the same project' do
+ let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [project]) }
+ let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) }
+
+ it 'succeeds for the right token' do
+ auth_success = Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
+ it 'fails for the wrong token' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+ end
+
+ context 'and belong to different projects' do
+ let!(:read_registry) { create(:deploy_token, username: 'deployer', read_repository: false, projects: [create(:project)]) }
+ let!(:read_repository) { create(:deploy_token, username: read_registry.username, read_registry: false, projects: [project]) }
+
+ it 'succeeds for the right token' do
+ auth_success = Gitlab::Auth::Result.new(read_repository, project, :deploy_token, [:download_code])
+
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', read_repository.token, project: project, ip: 'ip'))
+ .to eq(auth_success)
+ end
+
+ it 'fails for the wrong token' do
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'deployer')
+ expect(gl_auth.find_for_git_client('deployer', read_registry.token, project: project, ip: 'ip'))
+ .to eq(auth_failure)
+ end
+ end
+ end
+
context 'when the deploy token has read_repository as scope' do
let(:deploy_token) { create(:deploy_token, read_registry: false, projects: [project]) }
let(:login) { deploy_token.username }
diff --git a/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
new file mode 100644
index 00000000000..5938ecca459
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/fix_user_namespace_names_spec.rb
@@ -0,0 +1,104 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::FixUserNamespaceNames, :migration, schema: 20190620112608 do
+ let(:namespaces) { table(:namespaces) }
+ let(:users) { table(:users) }
+ let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
+
+ context 'updating the namespace names' do
+ it 'updates a user namespace within range' do
+ user2 = users.create(name: "Other user's full name", projects_limit: 10, username: 'also-not-null', email: '2')
+ user_namespace1 = namespaces.create(
+ id: 2,
+ owner_id: user.id,
+ name: "Should be the user's name",
+ path: user.username
+ )
+ user_namespace2 = namespaces.create(
+ id: 3,
+ owner_id: user2.id,
+ name: "Should also be the user's name",
+ path: user.username
+ )
+
+ described_class.new.perform(1, 5)
+
+ expect(user_namespace1.reload.name).to eq("The user's full name")
+ expect(user_namespace2.reload.name).to eq("Other user's full name")
+ end
+
+ it 'does not update namespaces out of range' do
+ user_namespace = namespaces.create(
+ id: 6,
+ owner_id: user.id,
+ name: "Should be the user's name",
+ path: user.username
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { user_namespace.reload.name }
+ end
+
+ it 'does not update groups owned by the users' do
+ user_group = namespaces.create(
+ id: 2,
+ owner_id: user.id,
+ name: 'A group name',
+ path: 'the-path',
+ type: 'Group'
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { user_group.reload.name }
+ end
+ end
+
+ context 'namespace route names' do
+ let(:routes) { table(:routes) }
+ let(:namespace) do
+ namespaces.create(
+ id: 2,
+ owner_id: user.id,
+ name: "Will be updated to the user's name",
+ path: user.username
+ )
+ end
+
+ it "updates the route name if it didn't match the namespace" do
+ route = routes.create(path: namespace.path, name: 'Incorrect name', source_type: 'Namespace', source_id: namespace.id)
+
+ described_class.new.perform(1, 5)
+
+ expect(route.reload.name).to eq("The user's full name")
+ end
+
+ it 'updates the route name if it was nil match the namespace' do
+ route = routes.create(path: namespace.path, name: nil, source_type: 'Namespace', source_id: namespace.id)
+
+ described_class.new.perform(1, 5)
+
+ expect(route.reload.name).to eq("The user's full name")
+ end
+
+ it "doesn't update group routes" do
+ route = routes.create(path: 'group-path', name: 'Group name', source_type: 'Group', source_id: namespace.id)
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { route.reload.name }
+ end
+
+ it "doesn't touch routes for namespaces out of range" do
+ user_namespace = namespaces.create(
+ id: 6,
+ owner_id: user.id,
+ name: "Should be the user's name",
+ path: user.username
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { user_namespace.reload.name }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
new file mode 100644
index 00000000000..d1d6d8411d1
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/fix_user_project_route_names_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::FixUserProjectRouteNames, :migration, schema: 20190620112608 do
+ let(:namespaces) { table(:namespaces) }
+ let(:users) { table(:users) }
+ let(:routes) { table(:routes) }
+ let(:projects) { table(:projects) }
+
+ let(:user) { users.create(name: "The user's full name", projects_limit: 10, username: 'not-null', email: '1') }
+
+ let(:namespace) do
+ namespaces.create(
+ owner_id: user.id,
+ name: "Should eventually be the user's name",
+ path: user.username
+ )
+ end
+
+ let(:project) do
+ projects.create(namespace_id: namespace.id, name: 'Project Name')
+ end
+
+ it "updates the route for a project if it did not match the user's name" do
+ route = routes.create(
+ id: 1,
+ path: "#{user.username}/#{project.path}",
+ source_id: project.id,
+ source_type: 'Project',
+ name: 'Completely wrong'
+ )
+
+ described_class.new.perform(1, 5)
+
+ expect(route.reload.name).to eq("The user's full name / Project Name")
+ end
+
+ it 'updates the route for a project if the name was nil' do
+ route = routes.create(
+ id: 1,
+ path: "#{user.username}/#{project.path}",
+ source_id: project.id,
+ source_type: 'Project',
+ name: nil
+ )
+
+ described_class.new.perform(1, 5)
+
+ expect(route.reload.name).to eq("The user's full name / Project Name")
+ end
+
+ it 'does not update routes that were are out of the range' do
+ route = routes.create(
+ id: 6,
+ path: "#{user.username}/#{project.path}",
+ source_id: project.id,
+ source_type: 'Project',
+ name: 'Completely wrong'
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { route.reload.name }
+ end
+
+ it 'does not update routes for projects in groups owned by the user' do
+ group = namespaces.create(
+ owner_id: user.id,
+ name: 'A group',
+ path: 'a-path',
+ type: ''
+ )
+ project = projects.create(namespace_id: group.id, name: 'Project Name')
+ route = routes.create(
+ id: 1,
+ path: "#{group.path}/#{project.path}",
+ source_id: project.id,
+ source_type: 'Project',
+ name: 'Completely wrong'
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { route.reload.name }
+ end
+
+ it 'does not update routes for namespaces' do
+ route = routes.create(
+ id: 1,
+ path: namespace.path,
+ source_id: namespace.id,
+ source_type: 'Namespace',
+ name: 'Completely wrong'
+ )
+
+ expect { described_class.new.perform(1, 5) }
+ .not_to change { route.reload.name }
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
new file mode 100644
index 00000000000..c45c64f6a23
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/migrate_null_private_profile_to_false_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::MigrateNullPrivateProfileToFalse, :migration, schema: 20190620105427 do
+ let(:users) { table(:users) }
+
+ it 'correctly migrates nil private_profile to false' do
+ private_profile_true = users.create!(private_profile: true, projects_limit: 1, email: 'a@b.com')
+ private_profile_false = users.create!(private_profile: false, projects_limit: 1, email: 'b@c.com')
+ private_profile_nil = users.create!(private_profile: nil, projects_limit: 1, email: 'c@d.com')
+
+ described_class.new.perform(private_profile_true.id, private_profile_nil.id)
+
+ private_profile_true.reload
+ private_profile_false.reload
+ private_profile_nil.reload
+
+ expect(private_profile_true.private_profile).to eq(true)
+ expect(private_profile_false.private_profile).to eq(false)
+ expect(private_profile_nil.private_profile).to eq(false)
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
index 4a81a37d341..ad4fa4fe03a 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -27,14 +27,19 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migra
merge_requests.create(params)
end
+ before do
+ create_merge_request(2, assignee_id: user.id)
+ create_merge_request(3, assignee_id: user_2.id)
+ create_merge_request(4, assignee_id: user_3.id)
+
+ # Test filtering MRs without assignees
+ create_merge_request(5, assignee_id: nil)
+ # Test filtering already migrated row
+ merge_request_assignees.create!(merge_request_id: 2, user_id: user_3.id)
+ end
+
describe '#perform' do
it 'creates merge_request_assignees rows according to merge_requests' do
- create_merge_request(2, assignee_id: user.id)
- create_merge_request(3, assignee_id: user_2.id)
- create_merge_request(4, assignee_id: user_3.id)
- # Test filtering already migrated row
- merge_request_assignees.create!(merge_request_id: 2, user_id: user_3.id)
-
subject.perform(1, 4)
rows = merge_request_assignees.order(:id).map { |row| row.attributes.slice('merge_request_id', 'user_id') }
@@ -53,4 +58,13 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migra
end
end
end
+
+ describe '#perform_all_sync' do
+ it 'executes peform for all merge requests in batches' do
+ expect(subject).to receive(:perform).with(2, 4).ordered
+ expect(subject).to receive(:perform).with(5, 5).ordered
+
+ subject.perform_all_sync(batch_size: 3)
+ end
+ end
end
diff --git a/spec/lib/gitlab/batch_pop_queueing_spec.rb b/spec/lib/gitlab/batch_pop_queueing_spec.rb
new file mode 100644
index 00000000000..28984d52024
--- /dev/null
+++ b/spec/lib/gitlab/batch_pop_queueing_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BatchPopQueueing do
+ include ExclusiveLeaseHelpers
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#initialize' do
+ where(:namespace, :queue_id, :expect_error, :error_type) do
+ 'feature' | '1' | false | nil
+ :feature | '1' | false | nil
+ nil | '1' | true | NoMethodError
+ 'feature' | nil | true | NoMethodError
+ '' | '1' | true | ArgumentError
+ 'feature' | '' | true | ArgumentError
+ 'feature' | 1 | true | NoMethodError
+ end
+
+ with_them do
+ it do
+ if expect_error
+ expect { described_class.new(namespace, queue_id) }.to raise_error(error_type)
+ else
+ expect { described_class.new(namespace, queue_id) }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ describe '#safe_execute', :clean_gitlab_redis_queues do
+ subject { queue.safe_execute(new_items, lock_timeout: lock_timeout) }
+
+ let(:queue) { described_class.new(namespace, queue_id) }
+ let(:namespace) { 'feature' }
+ let(:queue_id) { '1' }
+ let(:lock_timeout) { 10.minutes }
+ let(:new_items) { %w[A B] }
+ let(:lock_key) { queue.send(:lock_key) }
+ let(:queue_key) { queue.send(:queue_key) }
+
+ it 'enqueues new items always' do
+ Gitlab::Redis::Queues.with do |redis|
+ expect(redis).to receive(:sadd).with(queue_key, new_items)
+ expect(redis).to receive(:expire).with(queue_key, (lock_timeout + described_class::EXTRA_QUEUE_EXPIRE_WINDOW).to_i)
+ end
+
+ subject
+ end
+
+ it 'yields the new items with exclusive lease' do
+ uuid = 'test'
+ expect_to_obtain_exclusive_lease(lock_key, uuid, timeout: lock_timeout)
+ expect_to_cancel_exclusive_lease(lock_key, uuid)
+
+ expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
+ .to yield_with_args(match_array(new_items))
+ end
+
+ it 'returns the result and no items in the queue' do
+ expect(subject[:status]).to eq(:finished)
+ expect(subject[:new_items]).to be_empty
+
+ Gitlab::Redis::Queues.with do |redis|
+ expect(redis.llen(queue_key)).to be(0)
+ end
+ end
+
+ context 'when new items are enqueued during the process' do
+ it 'returns the result with newly added items' do
+ result = queue.safe_execute(new_items) do
+ queue.safe_execute(['C'])
+ end
+
+ expect(result[:status]).to eq(:finished)
+ expect(result[:new_items]).to eq(['C'])
+
+ Gitlab::Redis::Queues.with do |redis|
+ expect(redis.scard(queue_key)).to be(1)
+ end
+ end
+ end
+
+ context 'when interger items are enqueued' do
+ let(:new_items) { [1, 2, 3] }
+
+ it 'yields as String values' do
+ expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
+ .to yield_with_args(%w[1 2 3])
+ end
+ end
+
+ context 'when the queue key does not exist in Redis' do
+ before do
+ allow(queue).to receive(:enqueue) { }
+ end
+
+ it 'yields empty array' do
+ expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
+ .to yield_with_args([])
+ end
+ end
+
+ context 'when the other process has already been working on the queue' do
+ before do
+ stub_exclusive_lease_taken(lock_key, timeout: lock_timeout)
+ end
+
+ it 'does not yield the block' do
+ expect { |b| queue.safe_execute(new_items, lock_timeout: lock_timeout, &b) }
+ .not_to yield_control
+ end
+
+ it 'returns the result' do
+ expect(subject[:status]).to eq(:enqueued)
+ end
+ end
+
+ context 'when a duplicate item is enqueued' do
+ it 'returns the poped items to the queue and raise an error' do
+ expect { |b| queue.safe_execute(%w[1 1 2 2], &b) }
+ .to yield_with_args(match_array(%w[1 2]))
+ end
+ end
+
+ context 'when there are two queues' do
+ it 'enqueues items to each queue' do
+ queue_1 = described_class.new(namespace, '1')
+ queue_2 = described_class.new(namespace, '2')
+
+ result_2 = nil
+
+ result_1 = queue_1.safe_execute(['A']) do |_|
+ result_2 = queue_2.safe_execute(['B']) do |_|
+ queue_1.safe_execute(['C'])
+ queue_2.safe_execute(['D'])
+ end
+ end
+
+ expect(result_1[:status]).to eq(:finished)
+ expect(result_1[:new_items]).to eq(['C'])
+ expect(result_2[:status]).to eq(:finished)
+ expect(result_2[:new_items]).to eq(['D'])
+ end
+ end
+ end
+end
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..483c5ea9cff 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -26,7 +26,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/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index 7d750877d09..b3e58c3dfdb 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -10,7 +10,11 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
- project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request)
+ project: project,
+ current_user: user,
+ origin_ref: origin_ref,
+ merge_request: merge_request,
+ trigger_request: trigger_request)
end
let(:step) { described_class.new(pipeline, command) }
@@ -18,6 +22,7 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let(:ref) { 'master' }
let(:origin_ref) { ref }
let(:merge_request) { nil }
+ let(:trigger_request) { nil }
shared_context 'detached merge request pipeline' do
let(:merge_request) do
@@ -69,6 +74,43 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
end
end
+ context 'when pipeline triggered by legacy trigger' do
+ let(:user) { nil }
+ let(:trigger_request) do
+ build_stubbed(:ci_trigger_request, trigger: build_stubbed(:ci_trigger, owner: nil))
+ end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ step.perform!
+ end
+
+ it 'allows legacy triggers to create a pipeline' do
+ expect(pipeline).to be_valid
+ end
+
+ it 'does not break the chain' do
+ expect(step.break?).to eq false
+ end
+ end
+
+ context 'when :use_legacy_pipeline_triggers feature flag is disabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ step.perform!
+ end
+
+ it 'prevents legacy triggers from creating a pipeline' do
+ expect(pipeline.errors.to_a).to include /Trigger token is invalid/
+ end
+
+ it 'breaks the pipeline builder chain' do
+ expect(step.break?).to eq true
+ end
+ end
+ end
+
describe '#allowed_to_create?' do
subject { step.allowed_to_create? }
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 30ea3f3e28e..6ce4b321397 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -107,42 +107,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
expect(token.build.evaluate)
.to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
end
-
- context 'with the ci_variables_complex_expressions feature flag disabled' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- it 'is a greedy scanner for regexp boundaries' do
- scanner = StringScanner.new('/some .* / pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* / pattern')
- end
-
- it 'does not recognize the \ escape character for /' do
- scanner = StringScanner.new('/some .* \/ pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some .* \/ pattern')
- end
-
- it 'does not recognize the \ escape character for $' do
- scanner = StringScanner.new('/some numeric \$ pattern/')
-
- token = described_class.scan(scanner)
-
- expect(token).not_to be_nil
- expect(token.build.evaluate)
- .to eq Gitlab::UntrustedRegexp.new('some numeric \$ pattern')
- end
- end
end
describe '#evaluate' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index d8db9c262a1..7c98e729b0b 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -80,34 +80,6 @@ describe Gitlab::Ci::Pipeline::Expression::Lexer do
it { is_expected.to eq(tokens) }
end
end
-
- context 'with the ci_variables_complex_expressions feature flag turned off' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- it 'incorrectly tokenizes conjunctive match statements as one match statement' do
- tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/').tokens
-
- expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ && $EMPTY_VARIABLE =~ /nope/'])
- end
-
- it 'incorrectly tokenizes disjunctive match statements as one statement' do
- tokens = described_class.new('$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/').tokens
-
- expect(tokens.map(&:value)).to eq(['$PRESENT_VARIABLE', '=~', '/my var/ || $EMPTY_VARIABLE =~ /nope/'])
- end
-
- it 'raises an error about && operators' do
- expect { described_class.new('$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE').tokens }
- .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
- end
-
- it 'raises an error about || operators' do
- expect { described_class.new('$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE').tokens }
- .to raise_error(Gitlab::Ci::Pipeline::Expression::Lexer::SyntaxError).with_message('Unknown lexeme found!')
- end
- end
end
describe '#lexemes' do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index a2c2e3653d5..b259ef711aa 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,6 +1,4 @@
-# TODO switch this back after the "ci_variables_complex_expressions" feature flag is removed
-# require 'fast_spec_helper'
-require 'spec_helper'
+require 'fast_spec_helper'
require 'rspec-parameterized'
describe Gitlab::Ci::Pipeline::Expression::Statement do
@@ -118,54 +116,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do
expect(subject.evaluate).to eq(value)
end
end
-
- context 'with the ci_variables_complex_expressions feature flag disabled' do
- before do
- stub_feature_flags(ci_variables_complex_expressions: false)
- end
-
- where(:expression, :value) do
- '$PRESENT_VARIABLE == "my variable"' | true
- '"my variable" == $PRESENT_VARIABLE' | true
- '$PRESENT_VARIABLE == null' | false
- '$EMPTY_VARIABLE == null' | false
- '"" == $EMPTY_VARIABLE' | true
- '$EMPTY_VARIABLE' | ''
- '$UNDEFINED_VARIABLE == null' | true
- 'null == $UNDEFINED_VARIABLE' | true
- '$PRESENT_VARIABLE' | 'my variable'
- '$UNDEFINED_VARIABLE' | nil
- "$PRESENT_VARIABLE =~ /var.*e$/" | true
- "$PRESENT_VARIABLE =~ /^var.*/" | false
- "$EMPTY_VARIABLE =~ /var.*/" | false
- "$UNDEFINED_VARIABLE =~ /var.*/" | false
- "$PRESENT_VARIABLE =~ /VAR.*/i" | true
- '$PATH_VARIABLE =~ /path/variable/' | true
- '$PATH_VARIABLE =~ /path\/variable/' | true
- '$FULL_PATH_VARIABLE =~ /^/a/full/path/variable/value$/' | true
- '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true
- '$PRESENT_VARIABLE != "my variable"' | false
- '"my variable" != $PRESENT_VARIABLE' | false
- '$PRESENT_VARIABLE != null' | true
- '$EMPTY_VARIABLE != null' | true
- '"" != $EMPTY_VARIABLE' | false
- '$UNDEFINED_VARIABLE != null' | false
- 'null != $UNDEFINED_VARIABLE' | false
- "$PRESENT_VARIABLE !~ /var.*e$/" | false
- "$PRESENT_VARIABLE !~ /^var.*/" | true
- "$EMPTY_VARIABLE !~ /var.*/" | true
- "$UNDEFINED_VARIABLE !~ /var.*/" | true
- "$PRESENT_VARIABLE !~ /VAR.*/i" | false
- end
-
- with_them do
- let(:text) { expression }
-
- it "evaluates to `#{params[:value].inspect}`" do
- expect(subject.evaluate).to eq value
- end
- end
- end
end
describe '#truthful?' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 7991e2f48b5..46ea0d7554b 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -1,32 +1,30 @@
+# frozen_string_literal: true
+
require 'spec_helper'
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(:attributes) do
- { name: 'rspec', ref: 'master' }
- end
-
- subject do
- described_class.new(pipeline, attributes)
- end
+ let(:seed_build) { described_class.new(pipeline, attributes) }
describe '#attributes' do
- it 'returns hash attributes of a build' do
- expect(subject.attributes).to be_a Hash
- expect(subject.attributes)
- .to include(:name, :project, :ref)
- end
+ subject { seed_build.attributes }
+
+ it { is_expected.to be_a(Hash) }
+ it { is_expected.to include(:name, :project, :ref) }
end
describe '#bridge?' do
+ subject { seed_build.bridge? }
+
context 'when job is a bridge' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it { is_expected.to be_bridge }
+ it { is_expected.to be_truthy }
end
context 'when trigger definition is empty' do
@@ -34,20 +32,20 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
{ name: 'rspec', ref: 'master', options: { trigger: '' } }
end
- it { is_expected.not_to be_bridge }
+ it { is_expected.to be_falsey }
end
context 'when job is not a bridge' do
- it { is_expected.not_to be_bridge }
+ it { is_expected.to be_falsey }
end
end
describe '#to_resource' do
+ subject { seed_build.to_resource }
+
context 'when job is not a bridge' do
- it 'returns a valid build resource' do
- expect(subject.to_resource).to be_a(::Ci::Build)
- expect(subject.to_resource).to be_valid
- end
+ it { is_expected.to be_a(::Ci::Build) }
+ it { is_expected.to be_valid }
end
context 'when job is a bridge' do
@@ -55,71 +53,117 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it 'returns a valid bridge resource' do
- expect(subject.to_resource).to be_a(::Ci::Bridge)
- expect(subject.to_resource).to be_valid
- end
+ it { is_expected.to be_a(::Ci::Bridge) }
+ it { is_expected.to be_valid }
end
it 'memoizes a resource object' do
- build = subject.to_resource
-
- expect(build.object_id).to eq subject.to_resource.object_id
+ expect(subject.object_id).to eq seed_build.to_resource.object_id
end
it 'can not be persisted without explicit assignment' do
- build = subject.to_resource
-
pipeline.save!
- expect(build).not_to be_persisted
+ expect(subject).not_to be_persisted
end
end
- describe 'applying only/except policies' do
+ describe 'applying job inclusion policies' do
+ subject { seed_build }
+
context 'when no branch policy is specified' do
- let(:attributes) { { name: 'rspec' } }
+ let(:attributes) do
+ { name: 'rspec' }
+ end
it { is_expected.to be_included }
end
context 'when branch policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['deploy'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: ['deploy'] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['deploy'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: ['deploy'] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy] },
+ except: { refs: %w[deploy] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when branch regexp policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['/^deploy$/'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[/^deploy$/] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['/^deploy$/'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[/^deploy$/] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[/^deploy$/] },
+ except: { refs: %w[/^deploy$/] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when branch policy matches' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: %w[deploy master] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[deploy master] } }
+ end
it { is_expected.to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: %w[deploy master] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[deploy master] } }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy master] },
+ except: { refs: %w[deploy master] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -127,13 +171,29 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when keyword policy matches' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['branches'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[branches] } }
+ end
it { is_expected.to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['branches'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[branches] } }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches] },
+ except: { refs: %w[branches] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -141,50 +201,78 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when keyword policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['tags'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[tags] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['tags'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[tags] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[tags] },
+ except: { refs: %w[tags] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'with source-keyword policy' do
using RSpec::Parameterized
- let(:pipeline) { build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) }
+ let(:pipeline) do
+ build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
+ end
context 'matches' do
where(:keyword, :source) do
[
- %w(pushes push),
- %w(web web),
- %w(triggers trigger),
- %w(schedules schedule),
- %w(api api),
- %w(external external)
+ %w[pushes push],
+ %w[web web],
+ %w[triggers trigger],
+ %w[schedules schedule],
+ %w[api api],
+ %w[external external]
]
end
with_them do
context 'using an only policy' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
it { is_expected.to be_included }
end
context 'using an except policy' do
- let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
it { is_expected.not_to be_included }
end
context 'using both only and except policies' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -193,29 +281,39 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'non-matches' do
where(:keyword, :source) do
- %w(web trigger schedule api external).map { |source| ['pushes', source] } +
- %w(push trigger schedule api external).map { |source| ['web', source] } +
- %w(push web schedule api external).map { |source| ['triggers', source] } +
- %w(push web trigger api external).map { |source| ['schedules', source] } +
- %w(push web trigger schedule external).map { |source| ['api', source] } +
- %w(push web trigger schedule api).map { |source| ['external', source] }
+ %w[web trigger schedule api external].map { |source| ['pushes', source] } +
+ %w[push trigger schedule api external].map { |source| ['web', source] } +
+ %w[push web schedule api external].map { |source| ['triggers', source] } +
+ %w[push web trigger api external].map { |source| ['schedules', source] } +
+ %w[push web trigger schedule external].map { |source| ['api', source] } +
+ %w[push web trigger schedule api].map { |source| ['external', source] }
end
with_them do
context 'using an only policy' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
it { is_expected.not_to be_included }
end
context 'using an except policy' do
- let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
it { is_expected.to be_included }
end
context 'using both only and except policies' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -239,12 +337,24 @@ 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 using only' do
let(:attributes) do
- { name: 'rspec', only: { refs: ['branches@fork'] } }
+ { name: 'rspec', only: { refs: %w[branches@fork] } }
end
it { is_expected.not_to be_included }
@@ -252,11 +362,23 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when using except' do
let(:attributes) do
- { name: 'rspec', except: { refs: ['branches@fork'] } }
+ { name: 'rspec', except: { refs: %w[branches@fork] } }
end
it { is_expected.to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches@fork] },
+ except: { refs: %w[branches@fork] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index 546a9e7d0cc..3e5cd81f929 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -442,5 +442,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/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index 909dbffa38f..db31e5280a3 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -80,7 +80,7 @@ describe Gitlab::CurrentSettings do
# during the initialization phase of the test suite, so instead let's mock the internals of it
expect(ActiveRecord::Base.connection).not_to receive(:active?)
expect(ActiveRecord::Base.connection).not_to receive(:cached_table_exists?)
- expect(ActiveRecord::Migrator).not_to receive(:needs_migration?)
+ expect_any_instance_of(ActiveRecord::MigrationContext).not_to receive(:needs_migration?)
expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).to eq(0)
end
end
@@ -109,7 +109,7 @@ describe Gitlab::CurrentSettings do
context 'with pending migrations' do
before do
- expect(ActiveRecord::Migrator).to receive(:needs_migration?).and_return(true)
+ expect_any_instance_of(ActiveRecord::MigrationContext).to receive(:needs_migration?).and_return(true)
end
shared_examples 'a non-persisted ApplicationSetting object' do
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..b7a64adc2ff 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -9,12 +9,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_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
index c738cc49c1f..933f3c7896e 100644
--- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
@@ -5,40 +5,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 f8b103c0fab..5ee02650e49 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -7,7 +7,7 @@ describe 'cycle analytics events' do
let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
let(:events) do
- CycleAnalytics.new(project, { from: from_date, current_user: user })[stage].events
+ CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user })[stage].events
end
before do
diff --git a/spec/lib/gitlab/cycle_analytics/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_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
index 3b6af9cbaed..dea17e4f3dc 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
@@ -4,11 +4,11 @@ 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 +24,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 +36,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/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
index 506a8160412..3cd1320ca9c 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
@@ -8,7 +8,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 +18,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/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
index f072a9644e8..6d14973c711 100644
--- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
@@ -4,14 +4,14 @@ 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 +24,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..b001a46001e 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
@@ -2,7 +2,7 @@ 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..c146146723f 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
@@ -3,10 +3,10 @@ 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/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
index 17d5fbb9733..9ca12cc448c 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
@@ -5,16 +5,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 +28,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_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
index eacde22cd56..41028c44a00 100644
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -3,6 +3,40 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::TestStage do
let(:stage_name) { :test }
+ let(:project) { create(:project) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
it_behaves_like 'base stage'
+
+ describe '#median' do
+ before do
+ issue_1 = create(:issue, project: project, created_at: 90.minutes.ago)
+ issue_2 = create(:issue, project: project, created_at: 60.minutes.ago)
+ issue_3 = create(:issue, project: project, created_at: 60.minutes.ago)
+ mr_1 = create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago)
+ mr_2 = create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A')
+ mr_3 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
+ mr_4 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C')
+ mr_5 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'D')
+ mr_1.metrics.update!(latest_build_started_at: 32.minutes.ago, latest_build_finished_at: 2.minutes.ago)
+ mr_2.metrics.update!(latest_build_started_at: 62.minutes.ago, latest_build_finished_at: 32.minutes.ago)
+ mr_3.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+ mr_4.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+ mr_5.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+
+ create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
+ create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3)
+ create(:merge_requests_closing_issues, merge_request: mr_4, issue: issue_3)
+ create(:merge_requests_closing_issues, merge_request: mr_5, issue: issue_3)
+ end
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
+ end
+ end
end
diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
index a785b17f682..258ab049c0b 100644
--- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
@@ -28,27 +28,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::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 +69,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/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 92d90ac2fef..f11f68ab3c2 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -85,6 +85,20 @@ 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] }
@@ -224,4 +238,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/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..b73828b9554 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -9,35 +9,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
- context 'using MySQL' do
+ 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
+
+ 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 +98,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)
+ 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 '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 +140,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 +179,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 +211,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)
-
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- 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/)
- 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 +287,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 +346,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 +548,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)
-
- expect(model).to receive(:check_trigger_permissions!).with(:users)
-
- 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(: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
end
- context 'using PostgreSQL' do
- it 'renames a column concurrently' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ 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_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(: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
+ 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 +588,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 +623,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 +632,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 +851,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 +1210,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/db/importers/common_metrics_importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
index a717c8cd04d..eed2a1b7b48 100644
--- a/spec/db/importers/common_metrics_importer_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
@@ -1,31 +1,14 @@
# frozen_string_literal: true
require 'rails_helper'
-require Rails.root.join("db", "importers", "common_metrics_importer.rb")
-describe Importers::PrometheusMetric do
- let(:existing_group_titles) do
- ::PrometheusMetric::GROUP_DETAILS.each_with_object({}) do |(key, value), memo|
- memo[key] = value[:group_title]
- end
- end
-
- it 'group enum equals ::PrometheusMetric' do
- expect(described_class.groups).to eq(::PrometheusMetric.groups)
- end
-
- it 'GROUP_TITLES equals ::PrometheusMetric' do
- expect(described_class::GROUP_TITLES).to eq(existing_group_titles)
- end
-end
-
-describe Importers::CommonMetricsImporter do
+describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
subject { described_class.new }
context "does import common_metrics.yml" do
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
@@ -104,7 +87,7 @@ describe Importers::CommonMetricsImporter do
let(:query_identifier) { }
it 'raises exception' do
- expect { subject.execute }.to raise_error(described_class::MissingQueryId)
+ expect { subject.execute }.to raise_error(Gitlab::DatabaseImporters::CommonMetrics::Importer::MissingQueryId)
end
end
diff --git a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
new file mode 100644
index 00000000000..94f544e59b3
--- /dev/null
+++ b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do
+ it 'group enum equals ::PrometheusMetric' do
+ expect(described_class.groups).to eq(::PrometheusMetric.groups)
+ end
+
+ it '.group_titles equals ::PrometheusMetric' do
+ existing_group_titles = ::PrometheusMetricEnums.group_details.each_with_object({}) do |(key, value), memo|
+ memo[key] = value[:group_title]
+ end
+ expect(Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetricEnums.group_titles).to eq(existing_group_titles)
+ end
+end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 5f57cd6b825..9e8712266e8 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -24,21 +24,13 @@ describe Gitlab::Database do
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 } }
- end
-
describe '.postgresql?' do
subject { described_class.postgresql? }
@@ -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,7 +63,7 @@ describe Gitlab::Database do
end
describe '.postgresql_9_or_less?' do
- it 'returns false when using MySQL' do
+ it 'returns false when not using postgresql' do
allow(described_class).to receive(:postgresql?).and_return(false)
expect(described_class.postgresql_9_or_less?).to eq(false)
@@ -134,7 +117,7 @@ describe Gitlab::Database do
end
describe '.join_lateral_supported?' do
- it 'returns false when using MySQL' do
+ it 'returns false when not using postgresql' do
allow(described_class).to receive(:postgresql?).and_return(false)
expect(described_class.join_lateral_supported?).to eq(false)
@@ -156,7 +139,7 @@ describe Gitlab::Database do
end
describe '.replication_slots_supported?' do
- it 'returns false when using MySQL' do
+ it 'returns false when not using postgresql' do
allow(described_class).to receive(:postgresql?).and_return(false)
expect(described_class.replication_slots_supported?).to eq(false)
@@ -248,43 +231,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 +347,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 +412,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 +430,33 @@ 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
+ 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" => 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/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
index 48139c2f9dc..7833b9f387d 100644
--- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb
@@ -105,6 +105,7 @@ describe Gitlab::Email::Handler::CreateIssueHandler do
context "when the issue could not be saved" do
before do
allow_any_instance_of(Issue).to receive(:persisted?).and_return(false)
+ allow_any_instance_of(Issue).to receive(:ensure_metrics).and_return(nil)
end
it "raises an InvalidIssueError" do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index a28b95e5bff..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)
@@ -256,6 +262,22 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#submodule_urls_for' do
+ let(:ref) { 'master' }
+
+ it 'returns url mappings for submodules' do
+ urls = repository.submodule_urls_for(ref)
+
+ expect(urls).to eq({
+ "deeper/nested/six" => "git://github.com/randx/six.git",
+ "gitlab-grack" => "https://gitlab.com/gitlab-org/gitlab-grack.git",
+ "gitlab-shell" => "https://github.com/gitlabhq/gitlab-shell.git",
+ "nested/six" => "git://github.com/randx/six.git",
+ "six" => "git://github.com/randx/six.git"
+ })
+ end
+ end
+
describe '#commit_count' do
it { expect(repository.commit_count("master")).to eq(25) }
it { expect(repository.commit_count("feature")).to eq(9) }
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index f957ed00945..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,14 +16,38 @@ 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)
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_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index eed233f1f3e..e1d24ae8977 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -119,6 +119,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'
@@ -171,17 +184,6 @@ describe Gitlab::GitalyClient do
end
end
end
-
- context 'when catfile-cache feature is disabled' do
- before do
- stub_feature_flags({ 'gitaly_catfile-cache': false })
- end
-
- it 'does not set the gitaly-session-id in the metadata' do
- results = described_class.request_kwargs('default', nil)
- expect(results[:metadata]).not_to include('gitaly-session-id')
- end
- end
end
describe 'enforce_gitaly_request_limits?' do
diff --git a/spec/lib/gitlab/global_id_spec.rb b/spec/lib/gitlab/global_id_spec.rb
new file mode 100644
index 00000000000..d35b5da0b75
--- /dev/null
+++ b/spec/lib/gitlab/global_id_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GlobalId do
+ describe '.build' do
+ set(:object) { create(:issue) }
+
+ it 'returns a standard GlobalId if only object is passed' do
+ expect(described_class.build(object).to_s).to eq(object.to_global_id.to_s)
+ end
+
+ it 'returns a GlobalId from params' do
+ expect(described_class.build(model_name: 'MyModel', id: 'myid').to_s).to eq(
+ 'gid://gitlab/MyModel/myid'
+ )
+ end
+
+ it 'returns a GlobalId from object and `id` param' do
+ expect(described_class.build(object, id: 'myid').to_s).to eq(
+ 'gid://gitlab/Issue/myid'
+ )
+ end
+
+ it 'returns a GlobalId from object and `model_name` param' do
+ expect(described_class.build(object, model_name: 'MyModel').to_s).to eq(
+ "gid://gitlab/MyModel/#{object.id}"
+ )
+ end
+
+ it 'returns an error if model_name and id are not able to be determined' do
+ expect { described_class.build(id: 'myid') }.to raise_error(URI::InvalidComponentError)
+ expect { described_class.build(model_name: 'MyModel') }.to raise_error(URI::InvalidComponentError)
+ expect { described_class.build }.to raise_error(URI::InvalidComponentError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
new file mode 100644
index 00000000000..28056a6085d
--- /dev/null
+++ b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Representation::SubmoduleTreeEntry do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ describe '.decorate' do
+ let(:submodules) { repository.tree.submodules }
+
+ it 'returns array of SubmoduleTreeEntry' do
+ entries = described_class.decorate(submodules, repository.tree)
+
+ expect(entries.first).to be_a(described_class)
+
+ expect(entries.map(&:web_url)).to contain_exactly(
+ "https://gitlab.com/gitlab-org/gitlab-grack",
+ "https://github.com/gitlabhq/gitlab-shell",
+ "https://github.com/randx/six"
+ )
+
+ expect(entries.map(&:tree_url)).to contain_exactly(
+ "https://gitlab.com/gitlab-org/gitlab-grack/tree/645f6c4c82fd3f5e06f67134450a570b795e55a6",
+ "https://github.com/gitlabhq/gitlab-shell/tree/79bceae69cb5750d6567b223597999bfa91cb3b9",
+ "https://github.com/randx/six/tree/409f37c4f05865e4fb208c771485f211a22c4c2d"
+ )
+ end
+ end
+end
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..929b6222900 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -343,7 +343,6 @@ project:
- fork_network_member
- fork_network
- custom_attributes
-- prometheus_metrics
- lfs_file_locks
- project_badges
- source_of_merge_requests
diff --git a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
index 99669285d5b..873728f9909 100644
--- a/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
+++ b/spec/lib/gitlab/import_export/attribute_cleaner_spec.rb
@@ -22,7 +22,9 @@ describe Gitlab::ImportExport::AttributeCleaner do
'some_html' => '<p>dodgy html</p>',
'legit_html' => '<p>legit html</p>',
'_html' => '<p>perfectly ordinary html</p>',
- 'cached_markdown_version' => 12345
+ 'cached_markdown_version' => 12345,
+ 'group_id' => 99,
+ 'commit_id' => 99
}
end
@@ -31,7 +33,9 @@ describe Gitlab::ImportExport::AttributeCleaner do
'project_id' => 99,
'user_id' => 99,
'random_id_in_the_middle' => 99,
- 'notid' => 99
+ 'notid' => 99,
+ 'group_id' => 99,
+ 'commit_id' => 99
}
end
@@ -59,6 +63,6 @@ describe Gitlab::ImportExport::AttributeCleaner do
it 'does not remove excluded key if not listed' do
parsed_hash = described_class.clean(relation_hash: unsafe_hash, relation_class: relation_class)
- expect(parsed_hash.keys).to eq post_safe_hash.keys + excluded_keys
+ expect(parsed_hash.keys).to match_array post_safe_hash.keys + excluded_keys
end
end
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.json b/spec/lib/gitlab/import_export/project.json
index 8be074f4b9b..9e54ca28e58 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2775,7 +2775,8 @@
"action": 1,
"author_id": 1
}
- ]
+ ],
+ "approvals_before_merge": 1
},
{
"id": 26,
@@ -6630,8 +6631,16 @@
"id": 123,
"token": "cdbfasdf44a5958c83654733449e585",
"project_id": 5,
+ "owner_id": 1,
"created_at": "2017-01-16T15:25:28.637Z",
"updated_at": "2017-01-16T15:25:28.637Z"
+ },
+ {
+ "id": 456,
+ "token": "33a66349b5ad01fc00174af87804e40",
+ "project_id": 5,
+ "created_at": "2017-01-16T15:25:29.637Z",
+ "updated_at": "2017-01-16T15:25:29.637Z"
}
],
"deploy_keys": [],
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 ca46006ea58..3b7de185cf1 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -32,6 +32,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
context 'JSON' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ end
+
it 'restores models based on JSON' do
expect(@restored_project_json).to be_truthy
end
@@ -198,8 +202,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
context 'tokens are regenerated' do
- it 'has a new CI trigger token' do
- expect(Ci::Trigger.where(token: 'cdbfasdf44a5958c83654733449e585')).to be_empty
+ it 'has new CI trigger tokens' do
+ expect(Ci::Trigger.where(token: %w[cdbfasdf44a5958c83654733449e585 33a66349b5ad01fc00174af87804e40]))
+ .to be_empty
end
it 'has a new CI build token' do
@@ -212,7 +217,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(@project.merge_requests.size).to eq(9)
end
- it 'has the correct number of triggers' do
+ it 'only restores valid triggers' do
expect(@project.triggers.size).to eq(1)
end
@@ -491,6 +496,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)
@@ -523,6 +540,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..1ff2eb9210f 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
@@ -287,7 +291,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)
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/kubernetes/helm/delete_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
index cae92305b19..39a46f9bc6d 100644
--- a/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/delete_command_spec.rb
@@ -20,6 +20,15 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do
end
end
+ let(:tls_flags) do
+ <<~EOS.squish
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ EOS
+ end
+
context 'when there is a ca.pem file' do
let(:files) { { 'ca.pem': 'some file content' } }
@@ -27,7 +36,7 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
#{helm_delete_command}
EOS
end
@@ -35,10 +44,7 @@ describe Gitlab::Kubernetes::Helm::DeleteCommand do
let(:helm_delete_command) do
<<~EOS.squish
helm delete --purge app-name
- --tls
- --tls-ca-cert /data/helm/app-name/config/ca.pem
- --tls-cert /data/helm/app-name/config/cert.pem
- --tls-key /data/helm/app-name/config/key.pem
+ #{tls_flags}
EOS
end
end
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index db76d5d207e..7395b095454 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -36,7 +36,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_comand}
@@ -64,7 +64,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
@@ -93,7 +93,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
#{helm_install_command}
EOS
end
@@ -120,7 +120,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
/bin/date
@@ -151,7 +151,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
@@ -210,7 +210,7 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:commands) do
<<~EOS
helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
+ for i in $(seq 1 30); do helm version #{tls_flags} && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 978e64c4407..97ebb5f1554 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -176,6 +176,9 @@ describe Gitlab::Kubernetes::KubeClient do
let(:rbac_client) { client.rbac_client }
[
+ :create_role,
+ :get_role,
+ :update_role,
:create_cluster_role_binding,
:get_cluster_role_binding,
:update_cluster_role_binding
diff --git a/spec/lib/gitlab/kubernetes/role_binding_spec.rb b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
index 50acee254cb..4c200eb545f 100644
--- a/spec/lib/gitlab/kubernetes/role_binding_spec.rb
+++ b/spec/lib/gitlab/kubernetes/role_binding_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_name) { 'edit' }
+ let(:role_kind) { 'ClusterRole' }
let(:namespace) { 'my-namespace' }
let(:service_account_name) { 'my-service-account' }
@@ -20,7 +21,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
let(:role_ref) do
{
apiGroup: 'rbac.authorization.k8s.io',
- kind: 'ClusterRole',
+ kind: role_kind,
name: role_name
}
end
@@ -37,6 +38,7 @@ describe Gitlab::Kubernetes::RoleBinding, '#generate' do
described_class.new(
name: "gitlab-#{namespace}",
role_name: role_name,
+ role_kind: role_kind,
namespace: namespace,
service_account_name: service_account_name
).generate
diff --git a/spec/lib/gitlab/kubernetes/role_spec.rb b/spec/lib/gitlab/kubernetes/role_spec.rb
new file mode 100644
index 00000000000..3a5cd3b6704
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/role_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::Role do
+ let(:role) { described_class.new(name: name, namespace: namespace, rules: rules) }
+ let(:name) { 'example-name' }
+ let(:namespace) { 'example-namespace' }
+
+ let(:rules) do
+ [{
+ apiGroups: %w(hello.world),
+ resources: %w(oil diamonds coffee),
+ verbs: %w(say do walk run)
+ }]
+ end
+
+ describe '#generate' do
+ subject { role.generate }
+
+ let(:resource) do
+ ::Kubeclient::Resource.new(
+ metadata: { name: name, namespace: namespace },
+ rules: rules
+ )
+ end
+
+ it { is_expected.to eq(resource) }
+ end
+end
diff --git a/spec/lib/gitlab/lets_encrypt_spec.rb b/spec/lib/gitlab/lets_encrypt_spec.rb
index 674b114e9d3..65aea0937f1 100644
--- a/spec/lib/gitlab/lets_encrypt_spec.rb
+++ b/spec/lib/gitlab/lets_encrypt_spec.rb
@@ -10,21 +10,10 @@ describe ::Gitlab::LetsEncrypt do
end
describe '.enabled?' do
- let(:project) { create(:project) }
- let(:pages_domain) { create(:pages_domain, project: project) }
-
- subject { described_class.enabled?(pages_domain) }
+ subject { described_class.enabled? }
context 'when terms of service are accepted' do
it { is_expected.to eq(true) }
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(pages_auto_ssl: false)
- end
-
- it { is_expected.to eq(false) }
- end
end
context 'when terms of service are not accepted' do
@@ -34,23 +23,5 @@ describe ::Gitlab::LetsEncrypt do
it { is_expected.to eq(false) }
end
-
- context 'when feature flag for project is disabled' do
- before do
- stub_feature_flags(pages_auto_ssl_for_project: false)
- end
-
- it 'returns false' do
- is_expected.to eq(false)
- end
- end
-
- context 'when domain has not project' do
- let(:pages_domain) { create(:pages_domain) }
-
- it 'returns false' do
- is_expected.to eq(false)
- end
- end
end
end
diff --git a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
index 18052b1991c..c5fc74afea5 100644
--- a/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
+++ b/spec/lib/gitlab/markdown_cache/active_record/extension_spec.rb
@@ -9,12 +9,13 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do
cache_markdown_field :title, whitelisted: true
cache_markdown_field :description, pipeline: :single_line
- attr_accessor :author, :project
+ attribute :author
+ attribute :project
end
end
let(:cache_version) { Gitlab::MarkdownCache::CACHE_COMMONMARK_VERSION << 16 }
- let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { klass.create(title: markdown, title_html: html, cached_markdown_version: cache_version) }
let(:markdown) { '`Foo`' }
let(:html) { '<p data-sourcepos="1:1-1:5" dir="auto"><code>Foo</code></p>' }
@@ -37,7 +38,7 @@ describe Gitlab::MarkdownCache::ActiveRecord::Extension do
end
context 'a changed markdown field' do
- let(:thing) { klass.new(title: markdown, title_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { klass.create(title: markdown, title_html: html, cached_markdown_version: cache_version) }
before do
thing.title = updated_markdown
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/url_spec.rb b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
new file mode 100644
index 00000000000..34bc6359414
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::Url do
+ describe '#regex' do
+ it 'returns a regular expression' do
+ expect(described_class.regex).to be_a Regexp
+ end
+
+ it 'matches a metrics dashboard link with named params' do
+ url = Gitlab::Routing.url_helpers.metrics_namespace_project_environment_url('foo', 'bar', 1, start: 123345456, anchor: 'title')
+
+ expected_params = {
+ 'url' => url,
+ 'namespace' => 'foo',
+ 'project' => 'bar',
+ 'environment' => '1',
+ 'query' => '?start=123345456',
+ 'anchor' => '#title'
+ }
+
+ expect(described_class.regex).to match url
+
+ described_class.regex.match(url) do |m|
+ expect(m.named_captures).to eq expected_params
+ end
+ end
+
+ it 'does not match other gitlab urls that contain the term metrics' do
+ url = Gitlab::Routing.url_helpers.active_common_namespace_project_prometheus_metrics_url('foo', 'bar', :json)
+
+ expect(described_class.regex).not_to match url
+ end
+
+ it 'does not match other gitlab urls' do
+ url = Gitlab.config.gitlab.url
+
+ expect(described_class.regex).not_to match url
+ end
+
+ it 'does not match non-gitlab urls' do
+ url = 'https://www.super_awesome_site.com/'
+
+ expect(described_class.regex).not_to match url
+ end
+ end
+
+ describe '#build_dashboard_url' do
+ it 'builds the url for the dashboard endpoint' do
+ url = described_class.build_dashboard_url('foo', 'bar', 1)
+
+ expect(url).to match described_class.regex
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index 090e456644f..4b697b2ba0f 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
subject { described_class.new(1.second) }
describe '#sample' do
- let(:unicorn) { double('unicorn') }
+ let(:unicorn) { Module.new }
let(:raindrops) { double('raindrops') }
let(:stats) { double('stats') }
@@ -78,19 +78,32 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
end
end
- context 'additional metrics' do
- let(:unicorn_workers) { 2 }
-
+ context 'unicorn workers' do
before do
- allow(unicorn).to receive(:listener_names).and_return([""])
- allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14)
- allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers)
+ allow(unicorn).to receive(:listener_names).and_return([])
end
- it "sets additional metrics" do
- expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, unicorn_workers)
+ context 'without http server' do
+ it "does set unicorn_workers to 0" do
+ expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 0)
- subject.sample
+ subject.sample
+ end
+ end
+
+ context 'with http server' do
+ let(:http_server_class) { Struct.new(:worker_processes) }
+ let!(:http_server) { http_server_class.new(5) }
+
+ before do
+ stub_const('Unicorn::HttpServer', http_server_class)
+ end
+
+ it "sets additional metrics" do
+ expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 5)
+
+ subject.sample
+ end
end
end
end
diff --git a/spec/lib/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/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index f9c0daf1ef1..ef5c93e5c6b 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -83,5 +83,33 @@ describe Gitlab::OmniauthInitializer do
subject.execute([cas3_config])
end
+
+ it 'converts client_auth_method to a Symbol for openid_connect' do
+ openid_connect_config = {
+ 'name' => 'openid_connect',
+ 'args' => { name: 'openid_connect', client_auth_method: 'basic' }
+ }
+
+ expect(devise_config).to receive(:omniauth).with(
+ :openid_connect,
+ { name: 'openid_connect', client_auth_method: :basic }
+ )
+
+ subject.execute([openid_connect_config])
+ end
+
+ it 'converts client_auth_method to a Symbol for strategy_class OpenIDConnect' do
+ openid_connect_config = {
+ 'name' => 'openid_connect',
+ 'args' => { strategy_class: OmniAuth::Strategies::OpenIDConnect, client_auth_method: 'jwt_bearer' }
+ }
+
+ expect(devise_config).to receive(:omniauth).with(
+ :openid_connect,
+ { strategy_class: OmniAuth::Strategies::OpenIDConnect, client_auth_method: :jwt_bearer }
+ )
+
+ subject.execute([openid_connect_config])
+ end
end
end
diff --git a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
index 52c7a02219f..b6629fad453 100644
--- a/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/cache/map_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
@@ -28,6 +30,21 @@ describe Gitlab::PhabricatorImport::Cache::Map, :clean_gitlab_redis_cache do
expect(ttl).to be > 10.seconds
end
+
+ it 'sets the object in redis once if a block was given and nothing was cached' do
+ issue = create(:issue, project: project)
+
+ expect(map.get_gitlab_model('does not exist') { issue }).to eq(issue)
+
+ expect { |b| map.get_gitlab_model('does not exist', &b) }
+ .not_to yield_control
+ end
+
+ it 'does not cache `nil` objects' do
+ expect(map).not_to receive(:set_gitlab_model)
+
+ map.get_gitlab_model('does not exist') { nil }
+ end
end
describe '#set_gitlab_model' do
diff --git a/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
new file mode 100644
index 00000000000..e88eec2c393
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/user_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::User do
+ let(:user_client) do
+ described_class.new(phabricator_url: 'https://see-ya-later.phabricator', api_token: 'api-token')
+ end
+
+ describe '#users' do
+ let(:fake_client) { double('Phabricator client') }
+
+ before do
+ allow(user_client).to receive(:client).and_return(fake_client)
+ end
+
+ it 'calls the api with the correct params' do
+ expected_params = {
+ constraints: { phids: ['phid-1', 'phid-2'] }
+ }
+
+ expect(fake_client).to receive(:get).with('user.search',
+ params: expected_params)
+
+ user_client.users(['phid-1', 'phid-2'])
+ end
+
+ it 'returns an array of parsed responses' do
+ response = Gitlab::PhabricatorImport::Conduit::Response
+ .new(fixture_file('phabricator_responses/user.search.json'))
+
+ allow(fake_client).to receive(:get).and_return(response)
+
+ expect(user_client.users(%w[some phids])).to match_array([an_instance_of(Gitlab::PhabricatorImport::Conduit::UsersResponse)])
+ end
+
+ it 'performs multiple requests if more phids than the maximum page size are passed' do
+ stub_const('Gitlab::PhabricatorImport::Conduit::User::MAX_PAGE_SIZE', 1)
+ first_params = { constraints: { phids: ['phid-1'] } }
+ second_params = { constraints: { phids: ['phid-2'] } }
+
+ expect(fake_client).to receive(:get).with('user.search',
+ params: first_params).once
+ expect(fake_client).to receive(:get).with('user.search',
+ params: second_params).once
+
+ user_client.users(['phid-1', 'phid-2'])
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb b/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
new file mode 100644
index 00000000000..00778ad90fd
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/conduit/users_response_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Conduit::UsersResponse do
+ let(:conduit_response) do
+ Gitlab::PhabricatorImport::Conduit::Response
+ .new(JSON.parse(fixture_file('phabricator_responses/user.search.json')))
+ end
+
+ subject(:response) { described_class.new(conduit_response) }
+
+ describe '#users' do
+ it 'builds the correct users representation' do
+ tasks = response.users
+
+ usernames = tasks.map(&:username)
+
+ expect(usernames).to contain_exactly('jane', 'john')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
index 2412cf76f79..667321409da 100644
--- a/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/importer_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::PhabricatorImport::Issues::Importer do
- set(:project) { create(:project) }
+ let(:project) { create(:project) }
let(:response) do
Gitlab::PhabricatorImport::Conduit::TasksResponse.new(
@@ -15,7 +15,6 @@ describe Gitlab::PhabricatorImport::Issues::Importer do
before do
client = instance_double(Gitlab::PhabricatorImport::Conduit::Maniphest)
-
allow(client).to receive(:tasks).and_return(response)
allow(importer).to receive(:client).and_return(client)
end
@@ -34,20 +33,29 @@ describe Gitlab::PhabricatorImport::Issues::Importer do
importer.execute
end
- it 'schedules the next batch if there is one' do
- expect(Gitlab::PhabricatorImport::ImportTasksWorker)
- .to receive(:schedule).with(project.id, response.pagination.next_page)
+ context 'stubbed task import' do
+ before do
+ # Stub out the actual importing so we don't perform aditional requests
+ expect_next_instance_of(Gitlab::PhabricatorImport::Issues::TaskImporter) do |task_importer|
+ allow(task_importer).to receive(:execute)
+ end.at_least(1)
+ end
- importer.execute
- end
+ it 'schedules the next batch if there is one' do
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .to receive(:schedule).with(project.id, response.pagination.next_page)
- it 'does not reschedule when there is no next page' do
- allow(response.pagination).to receive(:has_next_page?).and_return(false)
+ importer.execute
+ end
- expect(Gitlab::PhabricatorImport::ImportTasksWorker)
- .not_to receive(:schedule)
+ it 'does not reschedule when there is no next page' do
+ allow(response.pagination).to receive(:has_next_page?).and_return(false)
- importer.execute
+ expect(Gitlab::PhabricatorImport::ImportTasksWorker)
+ .not_to receive(:schedule)
+
+ importer.execute
+ end
end
end
end
diff --git a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
index 1625604e754..06ed264e781 100644
--- a/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/issues/task_importer_spec.rb
@@ -12,6 +12,8 @@ describe Gitlab::PhabricatorImport::Issues::TaskImporter do
'description' => {
'raw' => '# This is markdown\n it can contain more text.'
},
+ 'authorPHID' => 'PHID-USER-456',
+ 'ownerPHID' => 'PHID-USER-123',
'dateCreated' => '1518688921',
'dateClosed' => '1518789995'
}
@@ -19,9 +21,18 @@ describe Gitlab::PhabricatorImport::Issues::TaskImporter do
)
end
+ subject(:importer) { described_class.new(project, task) }
+
describe '#execute' do
+ let(:fake_user_finder) { instance_double(Gitlab::PhabricatorImport::UserFinder) }
+
+ before do
+ allow(fake_user_finder).to receive(:find)
+ allow(importer).to receive(:user_finder).and_return(fake_user_finder)
+ end
+
it 'creates the issue with the expected attributes' do
- issue = described_class.new(project, task).execute
+ issue = importer.execute
expect(issue.project).to eq(project)
expect(issue).to be_persisted
@@ -34,21 +45,38 @@ describe Gitlab::PhabricatorImport::Issues::TaskImporter do
end
it 'does not recreate the issue when called multiple times' do
- expect { described_class.new(project, task).execute }
+ expect { importer.execute }
.to change { project.issues.reload.size }.from(0).to(1)
- expect { described_class.new(project, task).execute }
+ expect { importer.execute }
.not_to change { project.issues.reload.size }
end
it 'does not trigger a save when the object did not change' do
existing_issue = create(:issue,
task.issue_attributes.merge(author: User.ghost))
- importer = described_class.new(project, task)
allow(importer).to receive(:issue).and_return(existing_issue)
expect(existing_issue).not_to receive(:save!)
importer.execute
end
+
+ it 'links the author if the author can be found' do
+ author = create(:user)
+ expect(fake_user_finder).to receive(:find).with('PHID-USER-456').and_return(author)
+
+ issue = importer.execute
+
+ expect(issue.author).to eq(author)
+ end
+
+ it 'links an assignee if the user can be found' do
+ assignee = create(:user)
+ expect(fake_user_finder).to receive(:find).with('PHID-USER-123').and_return(assignee)
+
+ issue = importer.execute
+
+ expect(issue.assignees).to include(assignee)
+ end
end
end
diff --git a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
index dfbd8c546eb..5603a6961d6 100644
--- a/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/representation/task_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::Representation::Task do
@@ -7,6 +9,8 @@ describe Gitlab::PhabricatorImport::Representation::Task do
'phid' => 'the-phid',
'fields' => {
'name' => 'Title'.ljust(257, '.'), # A string padded to 257 chars
+ 'authorPHID' => 'a phid',
+ 'ownerPHID' => 'another user phid',
'description' => {
'raw' => '# This is markdown\n it can contain more text.'
},
@@ -30,4 +34,16 @@ describe Gitlab::PhabricatorImport::Representation::Task do
expect(task.issue_attributes).to eq(expected_attributes)
end
end
+
+ describe '#author_phid' do
+ it 'returns the correct field' do
+ expect(task.author_phid).to eq('a phid')
+ end
+ end
+
+ describe '#owner_phid' do
+ it 'returns the correct field' do
+ expect(task.owner_phid).to eq('another user phid')
+ end
+ end
end
diff --git a/spec/lib/gitlab/phabricator_import/representation/user_spec.rb b/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
new file mode 100644
index 00000000000..f52467a0cf1
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/representation/user_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::Representation::User do
+ subject(:user) do
+ described_class.new(
+ {
+ 'phid' => 'the-phid',
+ 'fields' => {
+ 'username' => 'the-username'
+ }
+ }
+ )
+ end
+
+ describe '#phabricator_id' do
+ it 'returns the phabricator id' do
+ expect(user.phabricator_id).to eq('the-phid')
+ end
+ end
+
+ describe '#username' do
+ it 'returns the username' do
+ expect(user.username).to eq('the-username')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
new file mode 100644
index 00000000000..096321cda5f
--- /dev/null
+++ b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
@@ -0,0 +1,89 @@
+require 'spec_helper'
+
+describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do
+ let(:project) { create(:project, namespace: create(:group)) }
+ subject(:finder) { described_class.new(project, ['first-phid', 'second-phid']) }
+
+ before do
+ project.namespace.add_developer(existing_user)
+ end
+
+ describe '#find' do
+ let!(:existing_user) { create(:user, username: 'existing-user') }
+ let(:cache) { Gitlab::PhabricatorImport::Cache::Map.new(project) }
+
+ before do
+ allow(finder).to receive(:object_map).and_return(cache)
+ end
+
+ context 'for a cached phid' do
+ before do
+ cache.set_gitlab_model(existing_user, 'first-phid')
+ end
+
+ it 'returns the existing user' do
+ expect(finder.find('first-phid')).to eq(existing_user)
+ end
+
+ it 'does not perform a find using the API' do
+ expect(finder).not_to receive(:find_user_for_phid)
+
+ finder.find('first-phid')
+ end
+
+ it 'excludes the phid from the request if one needs to be made' do
+ client = instance_double(Gitlab::PhabricatorImport::Conduit::User)
+ allow(finder).to receive(:client).and_return(client)
+
+ expect(client).to receive(:users).with(['second-phid']).and_return([])
+
+ finder.find('first-phid')
+ finder.find('second-phid')
+ end
+ end
+
+ context 'when the phid is not cached' do
+ let(:response) do
+ [
+ instance_double(
+ Gitlab::PhabricatorImport::Conduit::UsersResponse,
+ users: [instance_double(Gitlab::PhabricatorImport::Representation::User, phabricator_id: 'second-phid', username: 'existing-user')]
+ ),
+ instance_double(
+ Gitlab::PhabricatorImport::Conduit::UsersResponse,
+ users: [instance_double(Gitlab::PhabricatorImport::Representation::User, phabricator_id: 'first-phid', username: 'other-user')]
+ )
+ ]
+ end
+ let(:client) do
+ client = instance_double(Gitlab::PhabricatorImport::Conduit::User)
+ allow(client).to receive(:users).and_return(response)
+
+ client
+ end
+
+ before do
+ allow(finder).to receive(:client).and_return(client)
+ end
+
+ it 'loads the users from the API once' do
+ expect(client).to receive(:users).and_return(response).once
+
+ expect(finder.find('second-phid')).to eq(existing_user)
+ expect(finder.find('first-phid')).to be_nil
+ end
+
+ it 'adds found users to the cache' do
+ expect { finder.find('second-phid') }
+ .to change { cache.get_gitlab_model('second-phid') }
+ .from(nil).to(existing_user)
+ end
+
+ it 'only returns users that are members of the project' do
+ create(:user, username: 'other-user')
+
+ expect(finder.find('first-phid')).to eq(nil)
+ end
+ end
+ end
+end
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/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/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/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 253366e0789..f8b0cbfb6f6 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -353,7 +353,7 @@ describe Gitlab::UrlBlocker do
end
end
- describe '#validate_hostname!' do
+ describe '#validate_hostname' do
let(:ip_addresses) do
[
'2001:db8:1f70::999:de8:7648:6e8',
@@ -378,7 +378,7 @@ describe Gitlab::UrlBlocker do
it 'does not raise error for valid Ip addresses' do
ip_addresses.each do |ip|
- expect { described_class.send(:validate_hostname!, ip) }.not_to raise_error
+ expect { described_class.send(:validate_hostname, ip) }.not_to raise_error
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
new file mode 100644
index 00000000000..c34ac7867ab
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do
+ let(:redis_key) { 'foobar' }
+
+ subject { Class.new.extend(described_class) }
+
+ before do
+ stub_application_setting(usage_ping_enabled: setting_value)
+ end
+
+ context 'when usage_ping is disabled' do
+ let(:setting_value) { false }
+
+ it 'counter is not increased' do
+ expect do
+ subject.increment(redis_key)
+ end.not_to change { subject.total_count(redis_key) }
+ end
+ end
+
+ context 'when usage_ping is enabled' do
+ let(:setting_value) { true }
+
+ 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/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..41afbbb191c
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::WikiPageCounter, :clean_gitlab_redis_shared_state do
+ shared_examples :wiki_page_event do |event|
+ describe ".count(#{event})" do
+ it "increments the wiki page #{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})" 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
+
+ include_examples :wiki_page_event, :create
+ include_examples :wiki_page_event, :update
+ include_examples :wiki_page_event, :delete
+
+ describe 'totals' do
+ creations = 5
+ edits = 3
+ deletions = 2
+
+ before do
+ creations.times do
+ described_class.count(:create)
+ end
+ edits.times do
+ described_class.count(:update)
+ end
+ deletions.times do
+ described_class.count(:delete)
+ end
+ end
+
+ it 'can report all totals' do
+ expect(described_class.totals).to include(
+ wiki_pages_update: edits,
+ wiki_pages_create: creations,
+ wiki_pages_delete: deletions
+ )
+ end
+ end
+
+ describe 'unknown events' do
+ error = described_class::UnknownEvent
+
+ it 'cannot increment' do
+ expect { described_class.count(:wibble) }.to raise_error error
+ end
+
+ it 'cannot read' do
+ expect { described_class.read(:wibble) }.to raise_error error
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 67d49a30825..2289d906944 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -8,7 +8,7 @@ describe Gitlab::UsageData do
before do
create(:jira_service, project: projects[0])
create(:jira_service, project: projects[1])
- create(:jira_cloud_service, project: projects[2])
+ create(:jira_service, :jira_cloud_service, project: projects[2])
create(:prometheus_service, project: projects[1])
create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:service, project: projects[1], type: 'SlackService', active: true)
@@ -57,10 +57,18 @@ describe Gitlab::UsageData do
gitaly
database
avg_cycle_analytics
- web_ide_commits
influxdb_metrics_enabled
prometheus_metrics_enabled
))
+
+ expect(subject).to include(
+ 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)
+ )
end
it "gathers usage counts" do
@@ -182,6 +190,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 }
diff --git a/spec/lib/gitlab/web_ide_commits_counter_spec.rb b/spec/lib/gitlab/web_ide_commits_counter_spec.rb
deleted file mode 100644
index c51889a1c63..00000000000
--- a/spec/lib/gitlab/web_ide_commits_counter_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::WebIdeCommitsCounter, :clean_gitlab_redis_shared_state do
- describe '.increment' do
- it 'increments the web ide commits counter by 1' do
- expect do
- described_class.increment
- end.to change { described_class.total_count }.from(0).to(1)
- end
- end
-
- describe '.total_count' do
- it 'returns the total amount of web ide commits' do
- expect(described_class.total_count).to eq(0)
- 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
new file mode 100644
index 00000000000..52387fc3688
--- /dev/null
+++ b/spec/lib/gitlab/zoom_link_extractor_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ZoomLinkExtractor do
+ describe "#links" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:text, :links) do
+ 'issue text https://zoom.us/j/123 and https://zoom.us/s/1123433' | %w[https://zoom.us/j/123 https://zoom.us/s/1123433]
+ 'https://zoom.us/j/1123433 issue text' | %w[https://zoom.us/j/1123433]
+ 'issue https://zoom.us/my/1123433 text' | %w[https://zoom.us/my/1123433]
+ 'issue https://gitlab.com and https://gitlab.zoom.us/s/1123433' | %w[https://gitlab.zoom.us/s/1123433]
+ 'https://gitlab.zoom.us/j/1123433' | %w[https://gitlab.zoom.us/j/1123433]
+ 'https://gitlab.zoom.us/my/1123433' | %w[https://gitlab.zoom.us/my/1123433]
+ end
+
+ with_them do
+ subject { described_class.new(text).links }
+
+ it { is_expected.to eq(links) }
+ end
+ end
+end
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
new file mode 100644
index 00000000000..61096e6c69e
--- /dev/null
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Peek::Views::RedisDetailed, :request_store do
+ subject { described_class.new }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:cmd, :expected) do
+ [:auth, 'test'] | 'auth <redacted>'
+ [:set, 'key', 'value'] | 'set key <redacted>'
+ [:set, 'bad'] | 'set bad'
+ [:hmset, 'key1', 'value1', 'key2', 'value2'] | 'hmset key1 <redacted>'
+ [:get, 'key'] | 'get key'
+ end
+
+ with_them do
+ it 'scrubs Redis commands' do
+ subject.detail_store << { cmd: cmd, duration: 1.second }
+
+ 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..715b360953c
--- /dev/null
+++ b/spec/lib/peek/views/rugged_spec.rb
@@ -0,0 +1,41 @@
+# 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.raw, 'refs/heads/master'],
+ duration: 0.456)
+
+ expect(subject.duration).to be_within(0.00001).of(1.234)
+ expect(subject.calls).to eq(2)
+
+ results = subject.results
+ expect(results[:calls]).to eq(2)
+ expect(results[:duration]).to eq('1234.00ms')
+ expect(results[:details].count).to eq(2)
+
+ expect(results[:details][0][:args]).to eq([project.repository.raw.to_s, "refs/heads/master"])
+ expect(results[:details][1][:args]).to eq([project.repository.raw.to_s, "HEAD"])
+ 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..e7d500612b1
--- /dev/null
+++ b/spec/lib/prometheus/pid_provider_spec.rb
@@ -0,0 +1,79 @@
+# 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')
+ end
+
+ context 'when `Prometheus::Client::Support::Unicorn` provides worker_id' do
+ before do
+ expect(::Prometheus::Client::Support::Unicorn).to receive(:worker_id).and_return(1)
+ end
+
+ it { is_expected.to eq 'unicorn_1' }
+ end
+
+ context 'when no worker_id is provided from `Prometheus::Client::Support::Unicorn`' do
+ before do
+ expect(::Prometheus::Client::Support::Unicorn).to receive(:worker_id).and_return(nil)
+ end
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+ end
+
+ context 'when running in Puma mode' do
+ before do
+ stub_const('Puma', Module.new)
+ hide_const('Unicorn::Worker')
+ end
+
+ context 'when cluster worker id is specified in process name' do
+ before do
+ expect(described_class).to receive(:process_name).and_return('puma: cluster worker 1: 17483 [gitlab-puma-worker]')
+ end
+
+ it { is_expected.to eq 'puma_1' }
+ end
+
+ context 'when no worker id is specified in process name' do
+ before do
+ expect(described_class).to receive(:process_name).and_return('bin/puma')
+ end
+
+ 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/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/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb
index eae83cd64d3..c52e3c2191d 100644
--- a/spec/mailers/emails/pages_domains_spec.rb
+++ b/spec/mailers/emails/pages_domains_spec.rb
@@ -23,7 +23,7 @@ describe Emails::PagesDomains do
is_expected.to have_body_text(domain.domain)
is_expected.to have_body_text domain.url
is_expected.to have_body_text project_pages_domain_url(project, domain)
- is_expected.to have_body_text help_page_url('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record')
+ is_expected.to have_body_text help_page_url('user/project/pages/custom_domains_ssl_tls_certification/index.md', anchor: link_anchor)
end
end
end
@@ -50,6 +50,7 @@ describe Emails::PagesDomains do
describe '#pages_domain_enabled_email' do
let(:email_subject) { "#{project.path} | GitLab Pages domain '#{domain.domain}' has been enabled" }
+ let(:link_anchor) { 'steps' }
subject { Notify.pages_domain_enabled_email(domain, user) }
@@ -60,6 +61,7 @@ describe Emails::PagesDomains do
describe '#pages_domain_disabled_email' do
let(:email_subject) { "#{project.path} | GitLab Pages domain '#{domain.domain}' has been disabled" }
+ let(:link_anchor) { '4-verify-the-domains-ownership' }
subject { Notify.pages_domain_disabled_email(domain, user) }
@@ -72,6 +74,7 @@ describe Emails::PagesDomains do
describe '#pages_domain_verification_succeeded_email' do
let(:email_subject) { "#{project.path} | Verification succeeded for GitLab Pages domain '#{domain.domain}'" }
+ let(:link_anchor) { 'steps' }
subject { Notify.pages_domain_verification_succeeded_email(domain, user) }
@@ -82,6 +85,7 @@ describe Emails::PagesDomains do
describe '#pages_domain_verification_failed_email' do
let(:email_subject) { "#{project.path} | ACTION REQUIRED: Verification failed for GitLab Pages domain '#{domain.domain}'" }
+ let(:link_anchor) { 'steps' }
subject { Notify.pages_domain_verification_failed_email(domain, user) }
diff --git a/spec/migrations/active_record/schema_spec.rb b/spec/migrations/active_record/schema_spec.rb
index fbf5d387d0e..bc246f88685 100644
--- a/spec/migrations/active_record/schema_spec.rb
+++ b/spec/migrations/active_record/schema_spec.rb
@@ -15,7 +15,7 @@ describe ActiveRecord::Schema do
it '> schema version equals last migration timestamp' do
defined_schema_version = File.open(Rails.root.join('db', 'schema.rb')) do |file|
file.find { |line| line =~ /ActiveRecord::Schema.define/ }
- end.match(/(\d+)/)[0].to_i
+ end.match(/(\d{4}_\d{2}_\d{2}_\d{6})/)[0].to_i
expect(defined_schema_version).to eq(latest_migration_timestamp)
end
diff --git a/spec/migrations/fix_wrong_pages_access_level_spec.rb b/spec/migrations/fix_wrong_pages_access_level_spec.rb
new file mode 100644
index 00000000000..75ac5d919b2
--- /dev/null
+++ b/spec/migrations/fix_wrong_pages_access_level_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190703185326_fix_wrong_pages_access_level.rb')
+
+describe FixWrongPagesAccessLevel, :migration, :sidekiq, schema: 20190628185004 do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:migration_class) { described_class::MIGRATION }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ project_class = ::Gitlab::BackgroundMigration::FixPagesAccessLevel::Project
+ feature_class = ::Gitlab::BackgroundMigration::FixPagesAccessLevel::ProjectFeature
+
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:features_table) { table(:project_features) }
+
+ let(:subgroup) do
+ root_group = namespaces_table.create(path: "group", name: "group")
+ namespaces_table.create!(path: "subgroup", name: "group", parent_id: root_group.id)
+ end
+
+ def create_project_feature(path, project_visibility, pages_access_level)
+ project = projects_table.create!(path: path, visibility_level: project_visibility,
+ namespace_id: subgroup.id)
+ features_table.create!(project_id: project.id, pages_access_level: pages_access_level)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ first_id = create_project_feature("project1", project_class::PRIVATE, feature_class::PRIVATE).id
+ last_id = create_project_feature("project2", project_class::PRIVATE, feature_class::PUBLIC).id
+
+ migrate!
+
+ expect(migration_name).to be_scheduled_delayed_migration(2.minutes, first_id, last_id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+
+ def expect_migration
+ expect do
+ perform_enqueued_jobs do
+ migrate!
+ end
+ end
+ end
+
+ where(:project_visibility, :pages_access_level, :access_control_is_enabled,
+ :pages_deployed, :resulting_pages_access_level) do
+ # update settings for public projects regardless of access_control being enabled
+ project_class::PUBLIC | feature_class::PUBLIC | true | true | feature_class::ENABLED
+ project_class::PUBLIC | feature_class::PUBLIC | false | true | feature_class::ENABLED
+ # don't update public level for private and internal projects
+ project_class::PRIVATE | feature_class::PUBLIC | true | true | feature_class::PUBLIC
+ project_class::INTERNAL | feature_class::PUBLIC | true | true | feature_class::PUBLIC
+
+ # if access control is disabled but pages are deployed we make them public
+ project_class::INTERNAL | feature_class::ENABLED | false | true | feature_class::PUBLIC
+ # don't change anything if one of the conditions is not satisfied
+ project_class::INTERNAL | feature_class::ENABLED | true | true | feature_class::ENABLED
+ project_class::INTERNAL | feature_class::ENABLED | true | false | feature_class::ENABLED
+
+ # private projects
+ # if access control is enabled update pages_access_level to private regardless of deployment
+ project_class::PRIVATE | feature_class::ENABLED | true | true | feature_class::PRIVATE
+ project_class::PRIVATE | feature_class::ENABLED | true | false | feature_class::PRIVATE
+ # if access control is disabled and pages are deployed update pages_access_level to public
+ project_class::PRIVATE | feature_class::ENABLED | false | true | feature_class::PUBLIC
+ # if access control is disabled but pages aren't deployed update pages_access_level to private
+ project_class::PRIVATE | feature_class::ENABLED | false | false | feature_class::PRIVATE
+ end
+
+ with_them do
+ let!(:project_feature) do
+ create_project_feature("projectpath", project_visibility, pages_access_level)
+ end
+
+ before do
+ tested_path = File.join(Settings.pages.path, "group/subgroup/projectpath", "public")
+ allow(Dir).to receive(:exist?).with(tested_path).and_return(pages_deployed)
+
+ stub_pages_setting(access_control: access_control_is_enabled)
+ end
+
+ it "sets proper pages_access_level" do
+ expect(project_feature.reload.pages_access_level).to eq(pages_access_level)
+
+ perform_enqueued_jobs do
+ migrate!
+ end
+
+ expect(project_feature.reload.pages_access_level).to eq(resulting_pages_access_level)
+ end
+ end
+end
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index 2762eaeccd3..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
@@ -132,6 +132,19 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
expect(ActiveSession.sessions_from_ids([])).to eq([])
end
+
+ it 'uses redis lookup in batches' do
+ stub_const('ActiveSession::SESSION_BATCH_SIZE', 1)
+
+ redis = double(:redis)
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
+
+ sessions = ['session-a', 'session-b']
+ mget_responses = sessions.map { |session| [Marshal.dump(session)]}
+ expect(redis).to receive(:mget).twice.and_return(*mget_responses)
+
+ expect(ActiveSession.sessions_from_ids([1, 2])).to eql(sessions)
+ end
end
describe '.set' do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index d98db024f73..c30cb70e1c1 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -692,6 +692,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? }
@@ -2013,6 +2041,7 @@ describe Ci::Build do
{ key: 'CI', value: 'true', public: true, masked: false },
{ key: 'GITLAB_CI', value: 'true', public: true, masked: false },
{ key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false },
+ { key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host, public: true, masked: false },
{ key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false },
{ key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false },
{ key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false },
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/runner_spec.rb b/spec/models/ci/runner_spec.rb
index f735a89f69f..24ea059e871 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -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/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index fde8375f2a5..5b5d6f51b33 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -54,19 +54,31 @@ describe Ci::Trigger do
end
describe '#can_access_project?' do
+ let(:owner) { create(:user) }
let(:trigger) { create(:ci_trigger, owner: owner, project: project) }
context 'when owner is blank' do
- let(:owner) { nil }
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ trigger.update_attribute(:owner, nil)
+ end
subject { trigger.can_access_project? }
- it { is_expected.to eq(true) }
+ it { is_expected.to eq(false) }
+
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ subject { trigger.can_access_project? }
+
+ it { is_expected.to eq(true) }
+ end
end
context 'when owner is set' do
- let(:owner) { create(:user) }
-
subject { trigger.can_access_project? }
context 'and is member of the project' do
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 4f0cd0efe9c..4abe45a2152 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do
subject { gitlab_runner.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#install_command' do
@@ -156,4 +156,35 @@ describe Clusters::Applications::Runner do
end
end
end
+
+ describe '#prepare_uninstall' do
+ it 'pauses associated runner' do
+ active_runner = create(:ci_runner, contacted_at: 1.second.ago)
+
+ expect(active_runner.status).to eq(:online)
+
+ application_runner = create(:clusters_applications_runner, :scheduled, runner: active_runner)
+ application_runner.prepare_uninstall
+
+ expect(active_runner.status).to eq(:paused)
+ end
+ end
+
+ describe '#make_uninstalling!' do
+ subject { create(:clusters_applications_runner, :scheduled, runner: ci_runner) }
+
+ it 'calls prepare_uninstall' do
+ expect_any_instance_of(described_class).to receive(:prepare_uninstall).and_call_original
+
+ subject.make_uninstalling!
+ end
+ end
+
+ describe '#post_uninstall' do
+ it 'destroys its runner' do
+ application_runner = create(:clusters_applications_runner, :scheduled, runner: ci_runner)
+
+ expect { application_runner.post_uninstall }.to change { Ci::Runner.count }.by(-1)
+ end
+ end
end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index b96ca89c893..4a524b585e1 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -139,8 +139,8 @@ describe CommitRange do
end
describe '#has_been_reverted?' do
- let(:issue) { create(:issue) }
- let(:user) { issue.author }
+ let(:user) { create(:user) }
+ let(:issue) { create(:issue, author: user, project: project) }
it 'returns true if the commit has been reverted' do
create(:note_on_issue,
@@ -149,9 +149,11 @@ describe CommitRange do
note: commit1.revert_description(user),
project: issue.project)
- expect_any_instance_of(Commit).to receive(:reverts_commit?)
- .with(commit1, user)
- .and_return(true)
+ expect_next_instance_of(Commit) do |commit|
+ expect(commit).to receive(:reverts_commit?)
+ .with(commit1, user)
+ .and_return(true)
+ end
expect(commit1.has_been_reverted?(user, issue.notes_with_associations)).to eq(true)
end
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 0e5fb2b5153..9a12c3d6965 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -198,6 +198,36 @@ describe CacheMarkdownField, :clean_gitlab_redis_cache do
end
end
end
+
+ describe '#updated_cached_html_for' do
+ let(:thing) { klass.new(description: markdown, description_html: html, cached_markdown_version: cache_version) }
+
+ context 'when the markdown cache is outdated' do
+ before do
+ thing.cached_markdown_version += 1
+ end
+
+ it 'calls #refresh_markdown_cache' do
+ expect(thing).to receive(:refresh_markdown_cache)
+
+ expect(thing.updated_cached_html_for(:description)).to eq(html)
+ end
+ end
+
+ context 'when the markdown field does not exist' do
+ it 'returns nil' do
+ expect(thing.updated_cached_html_for(:something)).to eq(nil)
+ end
+ end
+
+ context 'when the markdown cache is up to date' do
+ it 'does not call #refresh_markdown_cache' do
+ expect(thing).not_to receive(:refresh_markdown_cache)
+
+ expect(thing.updated_cached_html_for(:description)).to eq(html)
+ end
+ end
+ end
end
context 'for Active record classes' do
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 e2fc8a5d127..c4f9f62ece5 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
describe DeploymentPlatform do
let(:project) { create(:project) }
- shared_examples '#deployment_platform' do
+ describe '#deployment_platform' do
subject { project.deployment_platform }
context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service' do
@@ -46,12 +46,11 @@ describe DeploymentPlatform do
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 }
+ 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
@@ -84,20 +82,4 @@ describe DeploymentPlatform do
end
end
end
-
- context 'legacy implementation' do
- before do
- stub_feature_flags(clusters_cte: false)
- end
-
- include_examples '#deployment_platform'
- end
-
- context 'CTE implementation' do
- before do
- stub_feature_flags(clusters_cte: true)
- end
-
- include_examples '#deployment_platform'
- end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 68224a56515..e19da41c3fe 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -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
new file mode 100644
index 00000000000..f5722f88aac
--- /dev/null
+++ b/spec/models/concerns/project_api_compatibility_spec.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectAPICompatibility do
+ let(:project) { create(:project) }
+
+ # git_strategy
+ it "converts build_git_strategy=fetch to build_allow_git_fetch=true" do
+ project.update!(build_git_strategy: 'fetch')
+ expect(project.build_allow_git_fetch).to eq(true)
+ end
+
+ it "converts build_git_strategy=clone to build_allow_git_fetch=false" do
+ project.update!(build_git_strategy: 'clone')
+ expect(project.build_allow_git_fetch).to eq(false)
+ end
+
+ 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)
+
+ expect(project.auto_devops_enabled?).to eq(expected)
+ end
+ end
+ end
+
+ 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/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 5aa43b58217..1fe176ab5af 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -4,7 +4,8 @@ require 'spec_helper'
describe ProjectFeaturesCompatibility do
let(:project) { create(:project) }
- let(:features) { %w(issues wiki builds merge_requests snippets) }
+ let(:features_except_repository) { %w(issues wiki builds merge_requests snippets) }
+ let(:features) { features_except_repository + ['repository'] }
# We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table
# All those fields got moved to a new table called project_feature and are now integers instead of booleans
@@ -12,30 +13,37 @@ describe ProjectFeaturesCompatibility do
# So we can keep it compatible
it "converts fields from 'true' to ProjectFeature::ENABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, "true")
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED)
end
end
it "converts fields from 'false' to ProjectFeature::DISABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, "false")
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED)
end
end
it "converts fields from true to ProjectFeature::ENABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, true)
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED)
end
end
it "converts fields from false to ProjectFeature::DISABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, false)
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED)
end
end
+
+ it "accepts private as ProjectFeature::PRIVATE" do
+ features.each do |feature|
+ project.update!("#{feature}_access_level".to_sym => 'private')
+ expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::PRIVATE)
+ end
+ end
end
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 1fb0dd5030c..31163a5bb5c 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -15,23 +15,46 @@ describe Group, 'Routable' do
end
describe 'Callbacks' do
- it 'creates route record on create' do
- expect(group.route.path).to eq(group.path)
- expect(group.route.name).to eq(group.name)
- end
+ context 'for a group' do
+ it 'creates route record on create' do
+ expect(group.route.path).to eq(group.path)
+ expect(group.route.name).to eq(group.name)
+ end
+
+ it 'updates route record on path change' do
+ group.update(path: 'wow', name: 'much')
- it 'updates route record on path change' do
- group.update(path: 'wow', name: 'much')
+ expect(group.route.path).to eq('wow')
+ expect(group.route.name).to eq('much')
+ end
+
+ it 'ensure route path uniqueness across different objects' do
+ create(:group, parent: group, path: 'xyz')
+ duplicate = build(:project, namespace: group, path: 'xyz')
- expect(group.route.path).to eq('wow')
- expect(group.route.name).to eq('much')
+ expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Path has already been taken')
+ end
end
- it 'ensure route path uniqueness across different objects' do
- create(:group, parent: group, path: 'xyz')
- duplicate = build(:project, namespace: group, path: 'xyz')
+ context 'for a user' do
+ let(:user) { create(:user, username: 'jane', name: "Jane Doe") }
- expect { duplicate.save! }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Path has already been taken')
+ it 'creates the route for a record on create' do
+ expect(user.namespace.name).to eq('Jane Doe')
+ expect(user.namespace.path).to eq('jane')
+ end
+
+ it 'updates routes and nested routes on name change' do
+ project = create(:project, path: 'work-stuff', name: 'Work stuff', namespace: user.namespace)
+
+ user.update!(username: 'jaen', name: 'Jaen Did')
+ project.reload
+
+ expect(user.namespace.name).to eq('Jaen Did')
+ expect(user.namespace.path).to eq('jaen')
+ expect(project.full_name).to eq('Jaen Did / Work stuff')
+ expect(project.full_path).to eq('jaen/work-stuff')
+ end
end
end
diff --git a/spec/models/concerns/stepable_spec.rb b/spec/models/concerns/stepable_spec.rb
new file mode 100644
index 00000000000..5685de6a9bf
--- /dev/null
+++ b/spec/models/concerns/stepable_spec.rb
@@ -0,0 +1,111 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Stepable do
+ let(:described_class) do
+ Class.new do
+ include Stepable
+
+ steps :method1, :method2, :method3
+
+ def execute
+ execute_steps
+ end
+
+ private
+
+ def method1
+ { status: :success }
+ end
+
+ def method2
+ return { status: :error } unless @pass
+
+ { status: :success, variable1: 'var1' }
+ end
+
+ def method3
+ { status: :success, variable2: 'var2' }
+ end
+ end
+ end
+
+ let(:prepended_module) do
+ Module.new do
+ extend ActiveSupport::Concern
+
+ prepended do
+ steps :appended_method1
+ end
+
+ private
+
+ def appended_method1
+ { status: :success }
+ end
+ end
+ end
+
+ before do
+ described_class.prepend(prepended_module)
+ end
+
+ it 'stops after the first error' do
+ expect(subject).not_to receive(:method3)
+ expect(subject).not_to receive(:appended_method1)
+
+ expect(subject.execute).to eq(
+ status: :error,
+ failed_step: :method2
+ )
+ end
+
+ context 'when all methods return success' do
+ before do
+ subject.instance_variable_set(:@pass, true)
+ end
+
+ it 'calls all methods in order' do
+ expect(subject).to receive(:method1).and_call_original.ordered
+ expect(subject).to receive(:method2).and_call_original.ordered
+ expect(subject).to receive(:method3).and_call_original.ordered
+ expect(subject).to receive(:appended_method1).and_call_original.ordered
+
+ subject.execute
+ end
+
+ it 'merges variables returned by all steps' do
+ expect(subject.execute).to eq(
+ status: :success,
+ variable1: 'var1',
+ variable2: 'var2'
+ )
+ end
+ end
+
+ context 'with multiple stepable classes' do
+ let(:other_class) do
+ Class.new do
+ include Stepable
+
+ steps :other_method1, :other_method2
+
+ private
+
+ def other_method1
+ { status: :success }
+ end
+
+ def other_method2
+ { status: :success }
+ end
+ end
+ end
+
+ it 'does not leak steps' do
+ expect(other_class.new.steps).to contain_exactly(:other_method1, :other_method2)
+ expect(subject.steps).to contain_exactly(:method1, :method2, :method3, :appended_method1)
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index b22a0340015..808659552ff 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#code' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
context 'with deployment' do
generate_cycle_analytics_spec(
@@ -37,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
@@ -67,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 07d60be091a..8cdf83b1292 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#issue' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :issue,
@@ -23,7 +24,7 @@ describe 'CycleAnalytics#issue' do
["list label added to issue",
-> (context, data) do
if data[:issue].persisted?
- data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id])
+ data[:issue].update(label_ids: [context.create(:list).label_id])
end
end]],
post_fn: -> (context, data) do
@@ -42,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 3d22a284264..28ad9bd194d 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#plan' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :plan,
@@ -24,7 +25,7 @@ describe 'CycleAnalytics#plan' do
end],
["list label added to issue",
-> (context, data) do
- data[:issue].update(label_ids: [context.create(:label, lists: [context.create(:list)]).id])
+ data[:issue].update(label_ids: [context.create(:list).label_id])
end]],
end_time_conditions: [["issue mentioned in a commit",
-> (context, data) do
@@ -46,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 383727cd8f7..613c1786540 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#production' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :production,
@@ -40,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
@@ -51,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_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
index 5d8b5b573cf..4de01b1c679 100644
--- a/spec/models/cycle_analytics_spec.rb
+++ b/spec/models/cycle_analytics/project_level_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CycleAnalytics do
+describe CycleAnalytics::ProjectLevel do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
@@ -11,9 +11,9 @@ describe CycleAnalytics do
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
- subject { described_class.new(project, from: from_date) }
+ subject { described_class.new(project, options: { from: from_date }) }
- describe '#all_medians_per_stage' do
+ describe '#all_medians_by_stage' do
before do
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
@@ -23,10 +23,10 @@ describe CycleAnalytics 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_per_stage).to eq(values)
+ expect(subject.all_medians_by_stage).to eq(values)
end
end
end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 1af5f9cc1f4..ef88fd86340 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#review' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :review,
@@ -27,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 8375944f03c..571792559d8 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -9,7 +9,7 @@ describe 'CycleAnalytics#staging' do
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :staging,
@@ -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 b78258df564..7b3001d2bd8 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#test' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :test,
@@ -35,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
@@ -46,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
@@ -61,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
@@ -76,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/discussion_spec.rb b/spec/models/discussion_spec.rb
index 22d4dab0617..950bdec4d00 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -10,6 +10,20 @@ describe Discussion do
let(:second_note) { create(:diff_note_on_merge_request, in_reply_to: first_note) }
let(:third_note) { create(:diff_note_on_merge_request) }
+ describe '.lazy_find' do
+ let!(:note1) { create(:discussion_note_on_merge_request).to_discussion }
+ let!(:note2) { create(:discussion_note_on_merge_request, in_reply_to: note1).to_discussion }
+
+ subject { [note1, note2].map { |note| described_class.lazy_find(note.discussion_id) } }
+
+ it 'batches requests' do
+ expect do
+ [described_class.lazy_find(note1.id),
+ described_class.lazy_find(note2.id)].map(&:__sync)
+ end.not_to exceed_query_limit(1)
+ end
+ end
+
describe '.build' do
it 'returns a discussion of the right type' do
discussion = described_class.build([first_note, second_note], merge_request)
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index fe4d64818b4..ce0681c4331 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -762,32 +762,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')
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 470ce65707d..90e0900445e 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -95,6 +95,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) }
@@ -994,4 +1031,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/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
index 07858fe8a70..7aa0d97b194 100644
--- a/spec/models/issue/metrics_spec.rb
+++ b/spec/models/issue/metrics_spec.rb
@@ -32,7 +32,7 @@ describe Issue::Metrics do
context "list labels" do
it "records the first time an issue is associated with a list label" do
- list_label = create(:label, lists: [create(:list)])
+ list_label = create(:list).label
time = Time.now
Timecop.freeze(time) { subject.update(label_ids: [list_label.id]) }
metrics = subject.metrics
@@ -43,9 +43,9 @@ describe Issue::Metrics do
it "does not record the second time an issue is associated with a list label" do
time = Time.now
- first_list_label = create(:label, lists: [create(:list)])
+ first_list_label = create(:list).label
Timecop.freeze(time) { subject.update(label_ids: [first_list_label.id]) }
- second_list_label = create(:label, lists: [create(:list)])
+ second_list_label = create(:list).label
Timecop.freeze(time + 5.hours) { subject.update(label_ids: [second_list_label.id]) }
metrics = subject.metrics
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 9b0c232f370..6a5bd276233 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -2454,6 +2454,13 @@ describe MergeRequest do
describe "#diff_refs" do
context "with diffs" do
subject { create(:merge_request, :with_diffs) }
+ let(:expected_diff_refs) do
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: subject.merge_request_diff.base_commit_sha,
+ start_sha: subject.merge_request_diff.start_commit_sha,
+ head_sha: subject.merge_request_diff.head_commit_sha
+ )
+ end
it "does not touch the repository" do
subject # Instantiate the object
@@ -2464,14 +2471,18 @@ describe MergeRequest do
end
it "returns expected diff_refs" do
- expected_diff_refs = Gitlab::Diff::DiffRefs.new(
- base_sha: subject.merge_request_diff.base_commit_sha,
- start_sha: subject.merge_request_diff.start_commit_sha,
- head_sha: subject.merge_request_diff.head_commit_sha
- )
-
expect(subject.diff_refs).to eq(expected_diff_refs)
end
+
+ context 'when importing' do
+ before do
+ subject.importing = true
+ end
+
+ it "returns MR diff_refs" do
+ expect(subject.diff_refs).to eq(expected_diff_refs)
+ end
+ end
end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 7a1ab20186a..03003e3dd7d 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -177,6 +177,7 @@ describe Note do
pipeline: :note,
cache_key: [note1, "note"],
project: note1.project,
+ rendered: note1.note_html,
author: note1.author
}
}]).and_call_original
@@ -189,6 +190,7 @@ describe Note do
pipeline: :note,
cache_key: [note2, "note"],
project: note2.project,
+ rendered: note2.note_html,
author: note2.author
}
}]).and_call_original
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 661957cf08b..519c519fbcf 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -53,24 +53,33 @@ describe PagesDomain do
end
let(:pages_domain) do
- build(:pages_domain, certificate: certificate, key: key).tap do |pd|
+ build(:pages_domain, certificate: certificate, key: key,
+ auto_ssl_enabled: auto_ssl_enabled).tap do |pd|
allow(pd).to receive(:project).and_return(project)
pd.valid?
end
end
- where(:pages_https_only, :certificate, :key, :errors_on) do
+ where(:pages_https_only, :certificate, :key, :auto_ssl_enabled, :errors_on) do
attributes = attributes_for(:pages_domain)
cert, key = attributes.fetch_values(:certificate, :key)
- true | nil | nil | %i(certificate key)
- true | cert | nil | %i(key)
- true | nil | key | %i(certificate key)
- true | cert | key | []
- false | nil | nil | []
- false | cert | nil | %i(key)
- false | nil | key | %i(key)
- false | cert | key | []
+ true | nil | nil | false | %i(certificate key)
+ true | nil | nil | true | []
+ true | cert | nil | false | %i(key)
+ true | cert | nil | true | %i(key)
+ true | nil | key | false | %i(certificate key)
+ true | nil | key | true | %i(key)
+ true | cert | key | false | []
+ true | cert | key | true | []
+ false | nil | nil | false | []
+ false | nil | nil | true | []
+ false | cert | nil | false | %i(key)
+ false | cert | nil | true | %i(key)
+ false | nil | key | false | %i(key)
+ false | nil | key | true | %i(key)
+ false | cert | key | false | []
+ false | cert | key | true | []
end
with_them do
@@ -118,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/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index 50c9d5968ac..31e55bf6be6 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -150,4 +150,32 @@ describe ProjectFeature do
end
end
end
+
+ describe 'default pages access level' do
+ subject { project.project_feature.pages_access_level }
+
+ before do
+ # project factory overrides all values in project_feature after creation
+ project.project_feature.destroy!
+ project.build_project_feature.save!
+ end
+
+ context 'when new project is private' do
+ let(:project) { create(:project, :private) }
+
+ it { is_expected.to eq(ProjectFeature::PRIVATE) }
+ end
+
+ context 'when new project is internal' do
+ let(:project) { create(:project, :internal) }
+
+ it { is_expected.to eq(ProjectFeature::PRIVATE) }
+ end
+
+ context 'when new project is public' do
+ let(:project) { create(:project, :public) }
+
+ it { is_expected.to eq(ProjectFeature::ENABLED) }
+ end
+ end
end
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index d5b0f94f461..74c85a13c88 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -44,7 +44,9 @@ describe BugzillaService do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
context 'when data are stored in properties' do
let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) { create(:bugzilla_service, properties: properties) }
+ let(:service) do
+ create(:bugzilla_service, :without_properties_callback, properties: properties)
+ end
include_examples 'issue tracker fields'
end
@@ -60,7 +62,7 @@ describe BugzillaService do
context 'when data are stored in both properties and separated fields' do
let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
let(:service) do
- create(:bugzilla_service, title: title, description: description, properties: properties)
+ create(:bugzilla_service, :without_properties_callback, title: title, description: description, properties: properties)
end
include_examples 'issue tracker fields'
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
index 56b0bda6626..5259357a254 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -58,7 +58,9 @@ describe CustomIssueTrackerService do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
context 'when data are stored in properties' do
let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) { create(:custom_issue_tracker_service, properties: properties) }
+ let(:service) do
+ create(:custom_issue_tracker_service, :without_properties_callback, properties: properties)
+ end
include_examples 'issue tracker fields'
end
@@ -74,7 +76,7 @@ describe CustomIssueTrackerService do
context 'when data are stored in both properties and separated fields' do
let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
let(:service) do
- create(:custom_issue_tracker_service, title: title, description: description, properties: properties)
+ create(:custom_issue_tracker_service, :without_properties_callback, title: title, description: description, properties: properties)
end
include_examples 'issue tracker fields'
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index a3726f09dc5..0c4fc290a13 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -61,7 +61,9 @@ describe GitlabIssueTrackerService do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
context 'when data are stored in properties' do
let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) { create(:gitlab_issue_tracker_service, properties: properties) }
+ let(:service) do
+ create(:gitlab_issue_tracker_service, :without_properties_callback, properties: properties)
+ end
include_examples 'issue tracker fields'
end
@@ -77,7 +79,7 @@ describe GitlabIssueTrackerService do
context 'when data are stored in both properties and separated fields' do
let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
let(:service) do
- create(:gitlab_issue_tracker_service, title: title, description: description, properties: properties)
+ create(:gitlab_issue_tracker_service, :without_properties_callback, title: title, description: description, properties: properties)
end
include_examples 'issue tracker fields'
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 9b122d85293..02060699e9a 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -145,7 +145,9 @@ describe JiraService do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
context 'when data are stored in properties' do
let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) { create(:jira_service, properties: properties) }
+ let(:service) do
+ create(:jira_service, :without_properties_callback, properties: properties)
+ end
include_examples 'issue tracker fields'
end
@@ -161,7 +163,7 @@ describe JiraService do
context 'when data are stored in both properties and separated fields' do
let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
let(:service) do
- create(:jira_service, title: title, description: description, properties: properties)
+ create(:jira_service, :without_properties_callback, title: title, description: description, properties: properties)
end
include_examples 'issue tracker fields'
@@ -234,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
@@ -604,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
@@ -619,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/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 806e3695962..c1ee6546b12 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -50,7 +50,9 @@ describe RedmineService do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
context 'when data are stored in properties' do
let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) { create(:redmine_service, properties: properties) }
+ let(:service) do
+ create(:redmine_service, :without_properties_callback, properties: properties)
+ end
include_examples 'issue tracker fields'
end
@@ -66,7 +68,7 @@ describe RedmineService do
context 'when data are stored in both properties and separated fields' do
let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
let(:service) do
- create(:redmine_service, title: title, description: description, properties: properties)
+ create(:redmine_service, :without_properties_callback, title: title, description: description, properties: properties)
end
include_examples 'issue tracker fields'
diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb
index b47ef6702b4..c48bf487af0 100644
--- a/spec/models/project_services/youtrack_service_spec.rb
+++ b/spec/models/project_services/youtrack_service_spec.rb
@@ -47,7 +47,9 @@ describe YoutrackService do
# this will be removed as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/63084
context 'when data are stored in properties' do
let(:properties) { access_params.merge(title: title, description: description) }
- let(:service) { create(:youtrack_service, properties: properties) }
+ let(:service) do
+ create(:youtrack_service, :without_properties_callback, properties: properties)
+ end
include_examples 'issue tracker fields'
end
@@ -63,7 +65,7 @@ describe YoutrackService do
context 'when data are stored in both properties and separated fields' do
let(:properties) { access_params.merge(title: 'wrong title', description: 'wrong description') }
let(:service) do
- create(:youtrack_service, title: title, description: description, properties: properties)
+ create(:youtrack_service, :without_properties_callback, title: title, description: description, properties: properties)
end
include_examples 'issue tracker fields'
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1bc092fa41a..9a083eee05e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -213,7 +213,7 @@ describe Project do
.only_integer
.is_greater_than_or_equal_to(10.minutes)
.is_less_than(1.month)
- .with_message('needs to be beetween 10 minutes and 1 month')
+ .with_message('needs to be between 10 minutes and 1 month')
end
it 'does not allow new projects beyond user limits' do
@@ -1190,6 +1190,14 @@ describe Project 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
@@ -1199,6 +1207,18 @@ describe Project do
end
end
+ describe '#pipelines_for' do
+ let(:project) { create(:project, :repository) }
+ let!(:pipeline) { create_pipeline(project) }
+ let!(:other_pipeline) { create_pipeline(project) }
+
+ context 'with implicit sha' do
+ subject { project.pipelines_for('master') }
+
+ it { is_expected.to contain_exactly(pipeline, other_pipeline) }
+ end
+ end
+
describe '#builds_enabled' do
let(:project) { create(:project) }
@@ -1675,26 +1695,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)
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index e14b19db915..687b0935c55 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
@@ -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_at: 3.hours.ago,
+ updated_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)
+ updated_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 3d967aa4ab8..12dff440ce2 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2426,16 +2426,15 @@ describe Repository do
# Gets the commit oid, and warms the cache
oid = project.commit.id
- expect(Gitlab::Git::Commit).not_to receive(:find).once
+ expect(Gitlab::Git::Commit).to receive(:find).once
- project.commit_by(oid: oid)
+ 2.times { project.commit_by(oid: oid) }
end
it 'caches nil values' do
expect(Gitlab::Git::Commit).to receive(:find).once
- project.commit_by(oid: '1' * 40)
- project.commit_by(oid: '1' * 40)
+ 2.times { project.commit_by(oid: '1' * 40) }
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index a4d177da0be..2d20f8c78cc 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -530,6 +530,17 @@ describe User do
end
describe 'before save hook' do
+ context '#default_private_profile_to_false' do
+ let(:user) { create(:user, private_profile: true) }
+
+ it 'converts nil to false' do
+ user.private_profile = nil
+ user.save!
+
+ expect(user.private_profile).to eq false
+ end
+ end
+
context 'when saving an external user' do
let(:user) { create(:user) }
let(:external_user) { create(:user, external: true) }
@@ -1127,6 +1138,7 @@ describe User do
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
expect(user.external).to be_falsey
+ expect(user.private_profile).to eq false
end
end
@@ -2931,7 +2943,7 @@ describe User do
let(:user) { create(:user, username: username) }
context 'when the user is updated' do
- context 'when the username is changed' do
+ context 'when the username or name is changed' do
let(:new_username) { 'bar' }
it 'changes the namespace (just to compare to when username is not changed)' do
@@ -2942,16 +2954,24 @@ describe User do
end.to change { user.namespace.updated_at }
end
- it 'updates the namespace name' do
+ it 'updates the namespace path when the username was changed' do
user.update!(username: new_username)
- expect(user.namespace.name).to eq(new_username)
+ expect(user.namespace.path).to eq(new_username)
end
- it 'updates the namespace path' do
- user.update!(username: new_username)
+ it 'updates the namespace name if the name was changed' do
+ user.update!(name: 'New name')
- expect(user.namespace.path).to eq(new_username)
+ expect(user.namespace.name).to eq('New name')
+ end
+
+ it 'updates nested routes for the namespace if the name was changed' do
+ project = create(:project, namespace: user.namespace)
+
+ user.update!(name: 'New name')
+
+ expect(project.route.reload.name).to include('New name')
end
context 'when there is a validation error (namespace name taken) while updating namespace' do
@@ -3484,4 +3504,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/ci/trigger_policy_spec.rb b/spec/policies/ci/trigger_policy_spec.rb
index d8a63066265..e9a85890082 100644
--- a/spec/policies/ci/trigger_policy_spec.rb
+++ b/spec/policies/ci/trigger_policy_spec.rb
@@ -3,52 +3,24 @@ require 'spec_helper'
describe Ci::TriggerPolicy do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
+ let(:trigger) { create(:ci_trigger, project: project, owner: create(:user)) }
- let(:policies) do
- described_class.new(user, trigger)
- end
-
- shared_examples 'allows to admin and manage trigger' do
- it 'does include ability to admin trigger' do
- expect(policies).to be_allowed :admin_trigger
- end
-
- it 'does include ability to manage trigger' do
- expect(policies).to be_allowed :manage_trigger
- end
- end
-
- shared_examples 'allows to manage trigger' do
- it 'does not include ability to admin trigger' do
- expect(policies).not_to be_allowed :admin_trigger
- end
-
- it 'does include ability to manage trigger' do
- expect(policies).to be_allowed :manage_trigger
- end
- end
-
- shared_examples 'disallows to admin and manage trigger' do
- it 'does not include ability to admin trigger' do
- expect(policies).not_to be_allowed :admin_trigger
- end
-
- it 'does not include ability to manage trigger' do
- expect(policies).not_to be_allowed :manage_trigger
- end
- end
+ subject { described_class.new(user, trigger) }
describe '#rules' do
context 'when owner is undefined' do
- let(:owner) { nil }
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: false)
+ trigger.update_attribute(:owner, nil)
+ end
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to admin and manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is developer of the project' do
@@ -56,35 +28,63 @@ describe Ci::TriggerPolicy do
project.add_developer(user)
end
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
- context 'when user is not member of the project' do
- it_behaves_like 'disallows to admin and manage trigger'
+ context 'when :use_legacy_pipeline_triggers feature flag is enabled' do
+ before do
+ stub_feature_flags(use_legacy_pipeline_triggers: true)
+ end
+
+ context 'when user is maintainer of the project' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.to be_allowed(:admin_trigger) }
+ end
+
+ context 'when user is developer of the project' do
+ before do
+ project.add_developer(user)
+ end
+
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
+ end
+
+ context 'when user is not member of the project' do
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
+ end
end
end
context 'when owner is an user' do
- let(:owner) { user }
+ before do
+ trigger.update!(owner: user)
+ end
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to admin and manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.to be_allowed(:admin_trigger) }
end
end
context 'when owner is another user' do
- let(:owner) { create(:user) }
-
context 'when user is maintainer of the project' do
before do
project.add_maintainer(user)
end
- it_behaves_like 'allows to manage trigger'
+ it { is_expected.to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is developer of the project' do
@@ -92,11 +92,13 @@ describe Ci::TriggerPolicy do
project.add_developer(user)
end
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
context 'when user is not member of the project' do
- it_behaves_like 'disallows to admin and manage trigger'
+ it { is_expected.not_to be_allowed(:manage_trigger) }
+ it { is_expected.not_to be_allowed(:admin_trigger) }
end
end
end
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 59f3a961d50..dc3675a7b9e 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -98,12 +98,38 @@ 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
+ allow(Group).to receive(:supports_nested_objects?).and_return(true)
+
+ 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
+ allow(Group).to receive(:supports_nested_objects?).and_return(true)
+
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
end
end
@@ -145,7 +171,8 @@ describe GroupPolicy do
it 'allows every owner permission except creating subgroups' do
create_subgroup_permission = [:create_subgroup]
- updated_owner_permissions = owner_permissions - create_subgroup_permission
+ updated_owner_permissions =
+ owner_permissions - create_subgroup_permission
expect_disallowed(*create_subgroup_permission)
expect_allowed(*updated_owner_permissions)
@@ -157,16 +184,32 @@ describe GroupPolicy do
it 'allows every owner permission except creating subgroups' do
create_subgroup_permission = [:create_subgroup]
- updated_owner_permissions = owner_permissions - create_subgroup_permission
+ updated_owner_permissions =
+ owner_permissions - create_subgroup_permission
expect_disallowed(*create_subgroup_permission)
expect_allowed(*updated_owner_permissions)
end
end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it 'allows every maintainer permission except creating subgroups' do
+ create_subgroup_permission = [:create_subgroup]
+ updated_maintainer_permissions =
+ maintainer_permissions - create_subgroup_permission
+
+ expect_disallowed(*create_subgroup_permission)
+ expect_allowed(*updated_maintainer_permissions)
+ end
+ 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) }
+ let(:nested_group) do
+ create(:group, :private, :owner_subgroup_creation_only, parent: group)
+ end
before do
nested_group.add_guest(guest)
@@ -461,6 +504,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..1534c572b30 100644
--- a/spec/presenters/blobs/unfold_presenter_spec.rb
+++ b/spec/presenters/blobs/unfold_presenter_spec.rb
@@ -54,8 +54,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
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 3df5d9412f8..e8e17228523 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -281,7 +281,7 @@ describe API::Commits do
end
it 'does not increment the usage counters using access token authentication' do
- expect(::Gitlab::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 +320,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
- 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 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
+
+ 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 }
+
+ before do
+ # initialize an empty repository to force fetching from the original project
+ forked_project.repository.create_if_not_exists
- expect(response).to have_gitlab_http_status(201)
+ 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 +457,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/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
new file mode 100644
index 00000000000..b04fcb9aece
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding a DiffNote' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project, :repository) }
+ let(:diff_refs) { noteable.diff_refs }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ body: 'Body text',
+ position: {
+ paths: {
+ old_path: 'files/ruby/popen.rb',
+ new_path: 'files/ruby/popen2.rb'
+ },
+ new_line: 14,
+ base_sha: diff_refs.base_sha,
+ head_sha: diff_refs.head_sha,
+ start_sha: diff_refs.start_sha
+ }
+ }
+
+ graphql_mutation(:create_diff_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_diff_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote
+
+ context do
+ let(:diff_refs) { build(:merge_request).diff_refs } # Allow fake diff refs so arguments are valid
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+ end
+
+ it 'returns the note with the correct position' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ mutation_position_response = mutation_response['note']['position']
+ expect(mutation_position_response['positionType']).to eq('text')
+ expect(mutation_position_response['filePath']).to eq('files/ruby/popen2.rb')
+ expect(mutation_position_response['oldPath']).to eq('files/ruby/popen.rb')
+ expect(mutation_position_response['newPath']).to eq('files/ruby/popen2.rb')
+ expect(mutation_position_response['newLine']).to eq(14)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
new file mode 100644
index 00000000000..3ba6c689024
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding an image DiffNote' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project, :repository) }
+ let(:diff_refs) { noteable.diff_refs }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ body: 'Body text',
+ position: {
+ paths: {
+ old_path: 'files/images/any_image.png',
+ new_path: 'files/images/any_image2.png'
+ },
+ width: 100,
+ height: 200,
+ x: 1,
+ y: 2,
+ base_sha: diff_refs.base_sha,
+ head_sha: diff_refs.head_sha,
+ start_sha: diff_refs.start_sha
+ }
+ }
+
+ graphql_mutation(:create_image_diff_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_image_diff_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote
+
+ context do
+ let(:diff_refs) { build(:merge_request).diff_refs } # Allow fake diff refs so arguments are valid
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+ end
+
+ it 'returns the note with the correct position' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ mutation_position_response = mutation_response['note']['position']
+ expect(mutation_position_response['filePath']).to eq('files/images/any_image2.png')
+ expect(mutation_position_response['oldPath']).to eq('files/images/any_image.png')
+ expect(mutation_position_response['newPath']).to eq('files/images/any_image2.png')
+ expect(mutation_position_response['positionType']).to eq('image')
+ expect(mutation_position_response['width']).to eq(100)
+ expect(mutation_position_response['height']).to eq(200)
+ expect(mutation_position_response['x']).to eq(1)
+ expect(mutation_position_response['y']).to eq(2)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
new file mode 100644
index 00000000000..14aaa430ac9
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding a Note' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project) }
+ let(:discussion) { nil }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
+ body: 'Body text'
+ }
+
+ graphql_mutation(:create_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors'
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+
+ it 'returns the note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ end
+
+ describe 'creating Notes in reply to a discussion' do
+ context 'when the user does not have permission to create notes on the discussion' do
+ let(:discussion) { create(:discussion_note).to_discussion }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ["The discussion does not exist or you don't have permission to perform this action"]
+ end
+
+ context 'when the user has permission to create notes on the discussion' do
+ let(:discussion) { create(:discussion_note, project: project).to_discussion }
+
+ it 'creates a Note in a discussion' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['discussion']['id']).to eq(discussion.to_global_id.to_s)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
new file mode 100644
index 00000000000..337a6e6f6e6
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Destroying a Note' do
+ include GraphqlHelpers
+
+ let!(:note) { create(:note) }
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(note).to_s
+ }
+
+ graphql_mutation(:destroy_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:destroy_note)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+
+ it 'does not destroy the Note' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Note.count }
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { note.author }
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Note'
+
+ it 'destroys the Note' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { Note.count }.by(-1)
+ end
+
+ it 'returns an empty Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('note')
+ expect(mutation_response['note']).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/update_spec.rb b/spec/requests/api/graphql/mutations/notes/update_spec.rb
new file mode 100644
index 00000000000..958f640995a
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/update_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Updating a Note' do
+ include GraphqlHelpers
+
+ let!(:note) { create(:note, note: original_body) }
+ let(:original_body) { 'Initial body text' }
+ let(:updated_body) { 'Updated body text' }
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(note).to_s,
+ body: updated_body
+ }
+
+ graphql_mutation(:update_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:update_note)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+
+ it 'does not update the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(original_body)
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { note.author }
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Note'
+
+ it 'updates the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(updated_body)
+ end
+
+ it 'returns the updated Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq(updated_body)
+ end
+
+ context 'when there are ActiveRecord validation errors' do
+ let(:updated_body) { '' }
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ["Note can't be blank"]
+
+ it 'does not update the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(original_body)
+ end
+
+ it 'returns the Note with its original body' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq(original_body)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index 67371cb35b6..54401ec4085 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -6,16 +6,6 @@ describe 'GraphQL' do
let(:query) { graphql_query_for('echo', 'text' => 'Hello world' ) }
- context 'graphql is disabled by feature flag' do
- before do
- stub_feature_flags(graphql: false)
- end
-
- it 'does not generate a route for GraphQL' do
- expect { post_graphql(query) }.to raise_error(ActionController::RoutingError)
- end
- end
-
context 'logging' do
shared_examples 'logging a graphql query' do
let(:expected_params) do
diff --git a/spec/requests/api/group_clusters_spec.rb b/spec/requests/api/group_clusters_spec.rb
new file mode 100644
index 00000000000..46e3dd650cc
--- /dev/null
+++ b/spec/requests/api/group_clusters_spec.rb
@@ -0,0 +1,452 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::GroupClusters do
+ include KubernetesHelpers
+
+ let(:current_user) { create(:user) }
+ let(:developer_user) { create(:user) }
+ let(:group) { create(:group, :private) }
+
+ before do
+ group.add_developer(developer_user)
+ group.add_maintainer(current_user)
+ end
+
+ describe 'GET /groups/:id/clusters' do
+ let!(:extra_cluster) { create(:cluster, :provided_by_gcp, :group) }
+
+ let!(:clusters) do
+ create_list(:cluster, 5, :provided_by_gcp, :group, :production_environment,
+ groups: [group])
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ get api("/groups/#{group.id}/clusters", developer_user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ get api("/groups/#{group.id}/clusters", current_user)
+ end
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'includes pagination headers' do
+ expect(response).to include_pagination_headers
+ end
+
+ it 'only include authorized clusters' do
+ cluster_ids = json_response.map { |cluster| cluster['id'] }
+
+ expect(cluster_ids).to match_array(clusters.pluck(:id))
+ expect(cluster_ids).not_to include(extra_cluster.id)
+ end
+ end
+ end
+
+ describe 'GET /groups/:id/clusters/:cluster_id' do
+ let(:cluster_id) { cluster.id }
+
+ let(:platform_kubernetes) do
+ create(:cluster_platform_kubernetes, :configured)
+ end
+
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_gcp, :with_domain,
+ platform_kubernetes: platform_kubernetes,
+ user: current_user,
+ groups: [group])
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ get api("/groups/#{group.id}/clusters/#{cluster_id}", developer_user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ get api("/groups/#{group.id}/clusters/#{cluster_id}", current_user)
+ end
+
+ it 'returns specific cluster' do
+ expect(json_response['id']).to eq(cluster.id)
+ end
+
+ it 'returns cluster information' do
+ expect(json_response['provider_type']).to eq('gcp')
+ expect(json_response['platform_type']).to eq('kubernetes')
+ expect(json_response['environment_scope']).to eq('*')
+ expect(json_response['cluster_type']).to eq('group_type')
+ expect(json_response['domain']).to eq('example.com')
+ end
+
+ it 'returns group information' do
+ cluster_group = json_response['group']
+
+ expect(cluster_group['id']).to eq(group.id)
+ expect(cluster_group['name']).to eq(group.name)
+ expect(cluster_group['web_url']).to eq(group.web_url)
+ end
+
+ it 'returns kubernetes platform information' do
+ platform = json_response['platform_kubernetes']
+
+ expect(platform['api_url']).to eq('https://kubernetes.example.com')
+ expect(platform['ca_cert']).to be_present
+ end
+
+ it 'returns user information' do
+ user = json_response['user']
+
+ expect(user['id']).to eq(current_user.id)
+ expect(user['username']).to eq(current_user.username)
+ end
+
+ it 'returns GCP provider information' do
+ gcp_provider = json_response['provider_gcp']
+
+ expect(gcp_provider['cluster_id']).to eq(cluster.id)
+ expect(gcp_provider['status_name']).to eq('created')
+ expect(gcp_provider['gcp_project_id']).to eq('test-gcp-project')
+ expect(gcp_provider['zone']).to eq('us-central1-a')
+ expect(gcp_provider['machine_type']).to eq('n1-standard-2')
+ expect(gcp_provider['num_nodes']).to eq(3)
+ expect(gcp_provider['endpoint']).to eq('111.111.111.111')
+ end
+
+ context 'when cluster has no provider' do
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_user,
+ groups: [group])
+ end
+
+ it 'does not include GCP provider info' do
+ expect(json_response['provider_gcp']).not_to be_present
+ end
+ end
+
+ context 'with non-existing cluster' do
+ let(:cluster_id) { 123 }
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+
+ shared_context 'kubernetes calls stubbed' do
+ before do
+ stub_kubeclient_discover(api_url)
+ end
+ end
+
+ describe 'POST /groups/:id/clusters/user' do
+ include_context 'kubernetes calls stubbed'
+
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:authorization_type) { 'rbac' }
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token',
+ authorization_type: authorization_type
+ }
+ end
+
+ let(:cluster_params) do
+ {
+ name: 'test-cluster',
+ domain: 'domain.example.com',
+ managed: false,
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
+ end
+
+ context 'with valid params' do
+ it 'responds with 201' do
+ expect(response).to have_gitlab_http_status(201)
+ end
+
+ it 'creates a new Cluster::Cluster' do
+ cluster_result = Clusters::Cluster.find(json_response["id"])
+ platform_kubernetes = cluster_result.platform
+
+ expect(cluster_result).to be_user
+ expect(cluster_result).to be_kubernetes
+ expect(cluster_result.group).to eq(group)
+ expect(cluster_result.name).to eq('test-cluster')
+ expect(cluster_result.domain).to eq('domain.example.com')
+ expect(cluster_result.managed).to be_falsy
+ expect(platform_kubernetes.rbac?).to be_truthy
+ expect(platform_kubernetes.api_url).to eq(api_url)
+ expect(platform_kubernetes.token).to eq('sample-token')
+ end
+ end
+
+ context 'when user does not indicate authorization type' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'sample-token'
+ }
+ end
+
+ it 'defaults to RBAC' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform_kubernetes.rbac?).to be_truthy
+ end
+ end
+
+ context 'when user sets authorization type as ABAC' do
+ let(:authorization_type) { 'abac' }
+
+ it 'creates an ABAC cluster' do
+ cluster_result = Clusters::Cluster.find(json_response['id'])
+
+ expect(cluster_result.platform.abac?).to be_truthy
+ end
+ end
+
+ context 'with invalid params' do
+ let(:api_url) { 'invalid_api_url' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'does not create a new Clusters::Cluster' do
+ expect(group.reload.clusters).to be_empty
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['platform_kubernetes.api_url'].first).to be_present
+ end
+ end
+ end
+
+ context 'when user tries to add multiple clusters' do
+ before do
+ create(:cluster, :provided_by_gcp, :group,
+ groups: [group])
+
+ post api("/groups/#{group.id}/clusters/user", current_user), params: cluster_params
+ end
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['base'].first).to include('Instance does not support multiple Kubernetes clusters')
+ end
+ end
+
+ context 'non-authorized user' do
+ before do
+ post api("/groups/#{group.id}/clusters/user", developer_user), params: cluster_params
+ end
+
+ it 'responds with 403' do
+ expect(response).to have_gitlab_http_status(403)
+
+ expect(json_response['message']).to eq('403 Forbidden')
+ end
+ end
+ end
+
+ describe 'PUT /groups/:id/clusters/:cluster_id' do
+ include_context 'kubernetes calls stubbed'
+
+ let(:api_url) { 'https://kubernetes.example.com' }
+
+ let(:update_params) do
+ {
+ domain: domain,
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ let(:domain) { 'new-domain.com' }
+ let(:platform_kubernetes_attributes) { {} }
+
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_gcp,
+ groups: [group], domain: 'old-domain.com')
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ put api("/groups/#{group.id}/clusters/#{cluster.id}", developer_user), params: update_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ put api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: update_params
+
+ cluster.reload
+ end
+
+ context 'with valid params' do
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'updates cluster attributes' do
+ expect(cluster.domain).to eq('new-domain.com')
+ end
+ end
+
+ context 'with invalid params' do
+ let(:domain) { 'invalid domain' }
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'does not update cluster attributes' do
+ expect(cluster.domain).to eq('old-domain.com')
+ end
+
+ it 'returns validation errors' do
+ expect(json_response['message']['domain'].first).to match('contains invalid characters (valid characters: [a-z0-9\\-])')
+ end
+ end
+
+ context 'with a GCP cluster' do
+ context 'when user tries to change GCP specific fields' do
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: 'https://new-api-url.com',
+ token: 'new-sample-token'
+ }
+ end
+
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns validation error' do
+ expect(json_response['message']['platform_kubernetes.base'].first).to eq('Cannot modify managed Kubernetes cluster')
+ end
+ end
+
+ context 'when user tries to change domain' do
+ let(:domain) { 'new-domain.com' }
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+
+ context 'with an user cluster' do
+ let(:api_url) { 'https://new-api-url.com' }
+
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_user,
+ groups: [group])
+ end
+
+ let(:platform_kubernetes_attributes) do
+ {
+ api_url: api_url,
+ token: 'new-sample-token'
+ }
+ end
+
+ let(:update_params) do
+ {
+ name: 'new-name',
+ platform_kubernetes_attributes: platform_kubernetes_attributes
+ }
+ end
+
+ it 'responds with 200' do
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'updates platform kubernetes attributes' do
+ platform_kubernetes = cluster.platform_kubernetes
+
+ expect(cluster.name).to eq('new-name')
+ expect(platform_kubernetes.api_url).to eq('https://new-api-url.com')
+ expect(platform_kubernetes.token).to eq('new-sample-token')
+ end
+ end
+
+ context 'with a cluster that does not belong to user' do
+ let(:cluster) { create(:cluster, :group, :provided_by_user) }
+
+ it 'responds with 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+
+ describe 'DELETE /groups/:id/clusters/:cluster_id' do
+ let(:cluster_params) { { cluster_id: cluster.id } }
+
+ let(:cluster) do
+ create(:cluster, :group, :provided_by_gcp,
+ groups: [group])
+ end
+
+ context 'non-authorized user' do
+ it 'responds with 403' do
+ delete api("/groups/#{group.id}/clusters/#{cluster.id}", developer_user), params: cluster_params
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+
+ context 'authorized user' do
+ before do
+ delete api("/groups/#{group.id}/clusters/#{cluster.id}", current_user), params: cluster_params
+ end
+
+ it 'responds with 204' do
+ expect(response).to have_gitlab_http_status(204)
+ end
+
+ it 'deletes the cluster' do
+ expect(Clusters::Cluster.exists?(id: cluster.id)).to be_falsy
+ end
+
+ context 'with a cluster that does not belong to user' do
+ let(:cluster) { create(:cluster, :group, :provided_by_user) }
+
+ it 'responds with 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index c41408fba65..52d926d5484 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -803,10 +803,10 @@ describe API::Groups 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
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index fcbff19bd61..3ab1818bebb 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -474,7 +474,7 @@ describe API::Internal do
'ssh',
{
authentication_abilities: [:read_project, :download_code, :push_code],
- namespace_path: project.namespace.name,
+ namespace_path: project.namespace.path,
project_path: project.path,
redirected_path: nil
}
@@ -753,7 +753,7 @@ describe API::Internal do
end
describe 'GET /internal/merge_request_urls' do
- let(:repo_name) { "#{project.namespace.name}/#{project.path}" }
+ let(:repo_name) { "#{project.full_path}" }
let(:changes) { URI.escape("#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch") }
before do
@@ -765,7 +765,7 @@ describe API::Internal do
expect(json_response).to match [{
"branch_name" => "new_branch",
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
"new_merge_request" => true
}]
end
@@ -786,7 +786,7 @@ describe API::Internal do
expect(json_response).to match [{
"branch_name" => "new_branch",
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch",
"new_merge_request" => true
}]
end
@@ -927,7 +927,7 @@ describe API::Internal do
expect(json_response['merge_request_urls']).to match [{
"branch_name" => branch_name,
- "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}",
+ "url" => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{branch_name}",
"new_merge_request" => true
}]
end
@@ -970,7 +970,7 @@ describe API::Internal do
expect(json_response['merge_request_urls']).to match [{
'branch_name' => branch_name,
- 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/1",
+ 'url' => "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/1",
'new_merge_request' => false
}]
end
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index a6e08ab3ab6..e8ed016db69 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -257,12 +257,22 @@ describe API::ProjectClusters do
post api("/projects/#{project.id}/clusters/user", current_user), params: cluster_params
end
+ it 'responds with 400' do
+ expect(response).to have_gitlab_http_status(400)
+
+ expect(json_response['message']['base'].first).to eq('Instance does not support multiple Kubernetes clusters')
+ end
+ end
+
+ context 'non-authorized user' do
+ before do
+ post api("/projects/#{project.id}/clusters/user", developer_user), params: cluster_params
+ end
+
it 'responds with 403' do
expect(response).to have_gitlab_http_status(403)
- end
- it 'returns an appropriate message' do
- expect(json_response['message']).to include('Instance does not support multiple Kubernetes clusters')
+ expect(json_response['message']).to eq('403 Forbidden')
end
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index c67412a44c1..fee300e9d7a 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -46,8 +46,6 @@ shared_examples 'languages and percentages JSON response' do
end
describe API::Projects do
- include ExternalAuthorizationServiceHelpers
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
@@ -1102,6 +1100,12 @@ describe API::Projects do
expect(json_response['wiki_enabled']).to be_present
expect(json_response['jobs_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['snippets_access_level']).to be_present
+ expect(json_response['repository_access_level']).to be_present
+ expect(json_response['issues_access_level']).to be_present
+ expect(json_response['merge_requests_access_level']).to be_present
+ expect(json_response['wiki_access_level']).to be_present
+ expect(json_response['builds_access_level']).to be_present
expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['created_at']).to be_present
@@ -1419,39 +1423,6 @@ describe API::Projects do
end
end
end
-
- context 'with external authorization' do
- let(:project) do
- create(:project,
- namespace: user.namespace,
- external_authorization_classification_label: 'the-label')
- end
-
- context 'when the user has access to the project' do
- before do
- external_service_allow_access(user, project)
- end
-
- it 'includes the label in the response' do
- get api("/projects/#{project.id}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['external_authorization_classification_label']).to eq('the-label')
- end
- end
-
- context 'when the external service denies access' do
- before do
- external_service_deny_access(user, project)
- end
-
- it 'returns a 404' do
- get api("/projects/#{project.id}", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
end
describe 'GET /projects/:id/users' do
@@ -1913,6 +1884,34 @@ describe API::Projects do
end
end
+ it 'updates builds_access_level' do
+ project_param = { builds_access_level: 'private' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['builds_access_level']).to eq('private')
+ end
+
+ it 'updates build_git_strategy' do
+ project_param = { build_git_strategy: 'clone' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['build_git_strategy']).to eq('clone')
+ end
+
+ it 'rejects to update build_git_strategy when build_git_strategy is invalid' do
+ project_param = { build_git_strategy: 'invalid' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
it 'updates merge_method' do
project_param = { merge_method: 'ff' }
@@ -1946,6 +1945,26 @@ describe API::Projects do
'-/system/project/avatar/'\
"#{project3.id}/banana_sample.gif")
end
+
+ it 'updates auto_devops_deploy_strategy' do
+ project_param = { auto_devops_deploy_strategy: 'timed_incremental' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['auto_devops_deploy_strategy']).to eq('timed_incremental')
+ end
+
+ it 'updates auto_devops_enabled' do
+ project_param = { auto_devops_enabled: false }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['auto_devops_enabled']).to eq(false)
+ end
end
context 'when authenticated as project maintainer' do
@@ -2007,20 +2026,6 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(403)
end
end
-
- context 'when updating external classification' do
- before do
- enable_external_authorization_service_check
- end
-
- it 'updates the classification label' do
- put(api("/projects/#{project.id}", user), params: { external_authorization_classification_label: 'new label' })
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(project.reload.external_authorization_classification_label).to eq('new label')
- end
- end
end
describe 'POST /projects/:id/archive' do
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index 3e0b478abb3..8abdcaa2e0e 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -89,7 +89,7 @@ describe API::Search do
it 'returns empty array' do
get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones).to be_empty
end
@@ -356,7 +356,7 @@ describe API::Search do
it 'returns empty array' do
get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
- milestones = JSON.parse(response.body)
+ milestones = json_response
expect(milestones).to be_empty
end
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 3f79e332b90..91cb8760a04 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -85,9 +85,7 @@ describe API::Services do
include_context service
# inject some properties into the service
- before do
- initialize_service(service)
- end
+ let!(:initialized_service) { initialize_service(service) }
it 'returns authentication error when unauthenticated' do
get api("/projects/#{project.id}/services/#{dashed_service}")
@@ -108,6 +106,15 @@ describe API::Services do
expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
end
+ it "returns empty hash if properties are empty" do
+ # deprecated services are not valid for update
+ initialized_service.update_attribute(:properties, {})
+ get api("/projects/#{project.id}/services/#{dashed_service}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['properties'].keys).to be_empty
+ end
+
it "returns error when authenticated but not a project owner" do
project.add_developer(user2)
get api("/projects/#{project.id}/services/#{dashed_service}", user2)
diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb
new file mode 100644
index 00000000000..c833bd047e2
--- /dev/null
+++ b/spec/requests/api/user_counts_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::UserCounts do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, title: "Test") }
+
+ describe 'GET /user_counts' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get api('/user_counts')
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns open counts for current user' do
+ get api('/user_counts', user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a Hash
+ expect(json_response['merge_requests']).to eq(1)
+ end
+
+ it 'updates the mr count when a new mr is assigned' do
+ create(:merge_request, source_project: project, author: user, assignees: [user])
+
+ get api('/user_counts', user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a Hash
+ expect(json_response['merge_requests']).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 46925daf40a..af2bee4563a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -448,6 +448,7 @@ describe API::Users do
it "returns 201 Created on success" do
post api("/users", admin), params: attributes_for(:user, projects_limit: 3)
+ expect(response).to match_response_schema('public_api/v4/user/admin')
expect(response).to have_gitlab_http_status(201)
end
@@ -643,6 +644,13 @@ describe API::Users do
describe "PUT /users/:id" do
let!(:admin_user) { create(:admin) }
+ it "returns 200 OK on success" do
+ put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
+
+ expect(response).to match_response_schema('public_api/v4/user/admin')
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it "updates user with new bio" do
put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
@@ -745,6 +753,14 @@ describe API::Users do
expect(user.reload.private_profile).to eq(true)
end
+ it "updates private profile when nil is given to false" do
+ admin.update(private_profile: true)
+
+ put api("/users/#{user.id}", admin), params: { private_profile: nil }
+
+ expect(user.reload.private_profile).to eq(false)
+ end
+
it "does not update admin status" do
put api("/users/#{admin_user.id}", admin), params: { can_create_group: false }
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index 1781759c54b..dc25e4d808e 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -1439,8 +1439,4 @@ describe 'Git LFS API and storage' do
post(url, params: params, headers: headers)
end
-
- def json_response
- @json_response ||= JSON.parse(response.body)
- end
end
diff --git a/spec/requests/lfs_locks_api_spec.rb b/spec/requests/lfs_locks_api_spec.rb
index 5b7b3d2fdd6..11436e5cd0c 100644
--- a/spec/requests/lfs_locks_api_spec.rb
+++ b/spec/requests/lfs_locks_api_spec.rb
@@ -163,8 +163,4 @@ describe 'Git LFS File Locking API' do
def do_get(url, params = nil, headers = nil)
get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
-
- def json_response
- @json_response ||= JSON.parse(response.body)
- end
end
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/api_routing_spec.rb b/spec/routing/api_routing_spec.rb
deleted file mode 100644
index 3c48ead4ff2..00000000000
--- a/spec/routing/api_routing_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe 'api', 'routing' do
- context 'when graphql is disabled' do
- before do
- stub_feature_flags(graphql: false)
- end
-
- it 'does not route to the GraphqlController' do
- expect(post('/api/graphql')).not_to route_to('graphql#execute')
- end
- end
-
- context 'when graphql is enabled' do
- before do
- stub_feature_flags(graphql: true)
- end
-
- it 'routes to the GraphqlController' do
- expect(post('/api/graphql')).to route_to('graphql#execute')
- end
- end
-end
diff --git a/spec/routing/environments_spec.rb b/spec/routing/environments_spec.rb
index aacbe300966..28b3e79c1ff 100644
--- a/spec/routing/environments_spec.rb
+++ b/spec/routing/environments_spec.rb
@@ -9,7 +9,7 @@ describe 'environments routing' do
end
let(:environments_route) do
- "#{project.namespace.name}/#{project.name}/environments/"
+ "#{project.full_path}/environments/"
end
describe 'routing environment folders' do
@@ -36,13 +36,12 @@ describe 'environments routing' do
end
def get_folder(folder)
- get("#{project.namespace.name}/#{project.name}/" \
- "environments/folders/#{folder}")
+ get("#{project.full_path}/environments/folders/#{folder}")
end
def folder_action(**opts)
- options = { namespace_id: project.namespace.name,
- project_id: project.name }
+ options = { namespace_id: project.namespace.path,
+ project_id: project.path }
['projects/environments#folder', options.merge(opts)]
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 6dde40d1cb6..8a3de2a52fc 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -713,4 +713,10 @@ describe 'project routing' do
end
end
end
+
+ describe Projects::DeployTokensController, 'routing' do
+ it 'routes to deploy_tokens#revoke' do
+ expect(put("/gitlab/gitlabhq/-/deploy_tokens/1/revoke")).to route_to("projects/deploy_tokens#revoke", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ end
+ end
end
diff --git a/spec/rubocop/cop/gitlab/rails_logger_spec.rb b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
new file mode 100644
index 00000000000..f0158ddcc5c
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/rails_logger'
+
+describe RuboCop::Cop::Gitlab::RailsLogger do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'flags the use of Rails.logger.error with a constant receiver' do
+ inspect_source("Rails.logger.error('some error')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'flags the use of Rails.logger.info with a constant receiver' do
+ inspect_source("Rails.logger.info('some info')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'flags the use of Rails.logger.warn with a constant receiver' do
+ inspect_source("Rails.logger.warn('some warning')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not flag the use of Rails.logger with a constant that is not Rails' do
+ inspect_source("AppLogger.error('some error')")
+
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ it 'does not flag the use of logger with a send receiver' do
+ inspect_source("file_logger.info('important info')")
+
+ expect(cop.offenses.size).to eq(0)
+ end
+end
diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
index ef20d9a1f26..fee390caa9f 100644
--- a/spec/rubocop/cop/qa/element_with_pattern_spec.rb
+++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
@@ -23,9 +23,9 @@ describe RuboCop::Cop::QA::ElementWithPattern do
expect_offense(<<-RUBY)
view 'app/views/shared/groups/_search_form.html.haml' do
element :groups_filter, 'search_field_tag :filter'
- ^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead.
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `data-qa-selector=groups_filter` instead.
element :groups_filter_placeholder, /Search by name/
- ^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead.
+ ^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `data-qa-selector=groups_filter_placeholder` instead.
end
RUBY
end
diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
index 89588b4df2b..dd5e43a4b62 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,
+ name: project.name,
+ path: project.namespace
}
end
let(:project) { create(:project) }
- let(:request) { EntityRequest.new(project: project, entity: :merge_request) }
+ 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..c9ffe1c5dad 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe AnalyticsIssueSerializer do
subject do
described_class
- .new(project: project, entity: :merge_request)
+ .new(entity: :merge_request)
.represent(resource)
end
@@ -16,7 +16,9 @@ describe AnalyticsIssueSerializer do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ name: project.name,
+ path: project.namespace
}
end
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index 62067cc0ef2..123d7d795ce 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe AnalyticsMergeRequestSerializer do
subject do
described_class
- .new(project: project, entity: :merge_request)
+ .new(entity: :merge_request)
.represent(resource)
end
@@ -17,7 +17,9 @@ describe AnalyticsMergeRequestSerializer do
id: "1",
state: 'open',
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ name: project.name,
+ path: project.namespace
}
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/diff_file_base_entity_spec.rb b/spec/serializers/diff_file_base_entity_spec.rb
new file mode 100644
index 00000000000..68c5c665ed6
--- /dev/null
+++ b/spec/serializers/diff_file_base_entity_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DiffFileBaseEntity do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ context 'diff for a changed submodule' do
+ let(:commit_sha_with_changed_submodule) do
+ "cfe32cf61b73a0d5e9f13e774abde7ff789b1660"
+ end
+ let(:commit) { project.commit(commit_sha_with_changed_submodule) }
+ let(:diff_file) { commit.diffs.diff_files.to_a.last }
+ let(:options) { { request: {}, submodule_links: Gitlab::SubmoduleLinks.new(repository) } }
+ let(:entity) { described_class.new(diff_file, options).as_json }
+
+ it do
+ expect(entity[:submodule]).to eq(true)
+ expect(entity[:submodule_link]).to eq("https://github.com/randx/six")
+ expect(entity[:submodule_tree_url]).to eq(
+ "https://github.com/randx/six/tree/409f37c4f05865e4fb208c771485f211a22c4c2d"
+ )
+ end
+ end
+end
diff --git a/spec/serializers/environment_status_entity_spec.rb b/spec/serializers/environment_status_entity_spec.rb
index f421432e8d6..cb4749f019f 100644
--- a/spec/serializers/environment_status_entity_spec.rb
+++ b/spec/serializers/environment_status_entity_spec.rb
@@ -70,7 +70,7 @@ describe EnvironmentStatusEntity do
it 'returns metrics url' do
expect(subject[:metrics_url])
- .to eq("/#{project.namespace.name}/#{project.name}/environments/#{environment.id}/deployments/#{deployment.iid}/metrics")
+ .to eq("/#{project.full_path}/environments/#{environment.id}/deployments/#{deployment.iid}/metrics")
end
end
diff --git a/spec/services/audit_event_service_spec.rb b/spec/services/audit_event_service_spec.rb
index 32fd98e6ef9..e42bff607b2 100644
--- a/spec/services/audit_event_service_spec.rb
+++ b/spec/services/audit_event_service_spec.rb
@@ -10,11 +10,8 @@ describe AuditEventService do
let(:logger) { instance_double(Gitlab::AuditJsonLogger) }
describe '#security_event' do
- before do
- expect(service).to receive(:file_logger).and_return(logger)
- end
-
it 'creates an event and logs to a file' do
+ expect(service).to receive(:file_logger).and_return(logger)
expect(logger).to receive(:info).with(author_id: user.id,
entity_id: project.id,
entity_type: "Project",
@@ -22,5 +19,32 @@ describe AuditEventService do
expect { service.security_event }.to change(SecurityEvent, :count).by(1)
end
+
+ it 'formats from and to fields' do
+ service = described_class.new(
+ user, project,
+ {
+ from: true,
+ to: false,
+ action: :create,
+ target_id: 1
+ })
+ expect(service).to receive(:file_logger).and_return(logger)
+ expect(logger).to receive(:info).with(author_id: user.id,
+ entity_type: 'Project',
+ entity_id: project.id,
+ from: 'true',
+ to: 'false',
+ action: :create,
+ target_id: 1)
+
+ expect { service.security_event }.to change(SecurityEvent, :count).by(1)
+
+ details = SecurityEvent.last.details
+ expect(details[:from]).to be true
+ expect(details[:to]).to be false
+ expect(details[:action]).to eq(:create)
+ expect(details[:target_id]).to eq(1)
+ end
end
end
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 16e2a2fba6b..cf84ec8fd4c 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -52,5 +52,91 @@ describe Boards::Issues::MoveService do
it_behaves_like 'issues move service', true
end
+
+ describe '#execute_multiple' do
+ set(:group) { create(:group) }
+ set(:user) { create(:user) }
+ set(:project) { create(:project, namespace: group) }
+ set(:board1) { create(:board, group: group) }
+ set(:development) { create(:group_label, group: group, name: 'Development') }
+ set(:testing) { create(:group_label, group: group, name: 'Testing') }
+ set(:list1) { create(:list, board: board1, label: development, position: 0) }
+ set(:list2) { create(:list, board: board1, label: testing, position: 1) }
+ let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns the expected result if list of issues is empty' do
+ expect(described_class.new(group, user, params).execute_multiple([])).to eq({ count: 0, success: false, issues: [] })
+ end
+
+ context 'moving multiple issues' do
+ let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }
+
+ it 'moves multiple issues from one list to another' do
+ expect(described_class.new(group, user, params).execute_multiple([issue1, issue2])).to be_truthy
+
+ expect(issue1.labels).to eq([testing])
+ expect(issue2.labels).to eq([testing])
+ end
+ end
+
+ context 'moving a single issue' do
+ let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
+
+ it 'moves one issue' do
+ expect(described_class.new(group, user, params).execute_multiple([issue1])).to be_truthy
+
+ expect(issue1.labels).to eq([testing])
+ end
+ end
+
+ context 'moving issues visually after an existing issue' do
+ let(:existing_issue) { create(:labeled_issue, project: project, labels: [testing], relative_position: 10) }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }
+
+ let(:move_params) do
+ params.dup.tap do |hash|
+ hash[:move_before_id] = existing_issue.id
+ end
+ end
+
+ it 'moves one issue' do
+ expect(described_class.new(group, user, move_params).execute_multiple([issue1, issue2])).to be_truthy
+
+ expect(issue1.labels).to eq([testing])
+ expect(issue2.labels).to eq([testing])
+
+ expect(issue1.relative_position > existing_issue.relative_position).to eq(true)
+ expect(issue2.relative_position > issue1.relative_position).to eq(true)
+ end
+ end
+
+ context 'moving issues visually before an existing issue' do
+ let(:existing_issue) { create(:labeled_issue, project: project, labels: [testing], relative_position: 10) }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [development]) }
+
+ let(:move_params) do
+ params.dup.tap do |hash|
+ hash[:move_after_id] = existing_issue.id
+ end
+ end
+
+ it 'moves one issue' do
+ expect(described_class.new(group, user, move_params).execute_multiple([issue1, issue2])).to be_truthy
+
+ expect(issue1.labels).to eq([testing])
+ expect(issue2.labels).to eq([testing])
+
+ expect(issue2.relative_position < existing_issue.relative_position).to eq(true)
+ expect(issue1.relative_position < issue2.relative_position).to eq(true)
+ end
+ end
+ end
end
end
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/clusters/applications/check_uninstall_progress_service_spec.rb b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
index 9ab83d913f5..a948b442441 100644
--- a/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_uninstall_progress_service_spec.rb
@@ -41,7 +41,7 @@ describe Clusters::Applications::CheckUninstallProgressService do
end
end
- context 'when application is installing' do
+ context 'when application is uninstalling' do
RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
context 'when installation POD succeeded' do
@@ -56,6 +56,12 @@ describe Clusters::Applications::CheckUninstallProgressService do
service.execute
end
+ it 'runs application post_uninstall' do
+ expect(application).to receive(:post_uninstall).and_call_original
+
+ service.execute
+ end
+
it 'destroys the application' do
expect(worker_class).not_to receive(:perform_in)
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 be052a07da7..44407ae2793 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
@@ -34,6 +34,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
stub_kubeclient_create_service_account(api_url, namespace: namespace)
stub_kubeclient_create_secret(api_url, namespace: namespace)
stub_kubeclient_put_secret(api_url, "#{namespace}-token", namespace: namespace)
+ stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
+ stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
stub_kubeclient_get_secret(
api_url,
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
index 382b9043566..8b874989758 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_or_update_service_account_service_spec.rb
@@ -143,6 +143,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
stub_kubeclient_get_role_binding_error(api_url, role_binding_name, namespace: namespace)
stub_kubeclient_create_role_binding(api_url, namespace: namespace)
+ stub_kubeclient_put_role(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME, namespace: namespace)
+ stub_kubeclient_put_role_binding(api_url, Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
end
it_behaves_like 'creates service account and token'
@@ -169,6 +171,24 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService do
)
)
end
+
+ it 'creates a role and role binding granting knative serving permissions to the service account' do
+ subject
+
+ expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME}").with(
+ body: hash_including(
+ metadata: {
+ name: Clusters::Gcp::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_NAME,
+ namespace: namespace
+ },
+ rules: [{
+ apiGroups: %w(serving.knative.dev),
+ resources: %w(configurations configurationgenerations routes revisions revisionuids autoscalers services),
+ verbs: %w(get list create update delete patch watch)
+ }]
+ )
+ )
+ end
end
end
end
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index c5ff6cdbacd..a7c95428485 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -87,6 +87,14 @@ describe Groups::CreateService, '#execute' do
it { is_expected.to be_persisted }
end
+
+ context 'as maintainer' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it { is_expected.to be_persisted }
+ end
end
end
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index aebc5ba2874..fb12877fa05 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -11,342 +11,380 @@ describe Issuable::BulkUpdateService do
.reverse_merge(issuable_ids: Array(issuables).map(&:id).join(','))
type = Array(issuables).first.model_name.param_key
- Issuable::BulkUpdateService.new(project, user, bulk_update_params).execute(type)
+ Issuable::BulkUpdateService.new(user, bulk_update_params).execute(type)
end
- describe 'close issues' do
- let(:issues) { create_list(:issue, 2, project: project) }
-
- it 'succeeds and returns the correct number of issues updated' do
- result = bulk_update(issues, state_event: 'close')
+ shared_examples 'updates milestones' do
+ it 'succeeds' do
+ result = bulk_update(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 'closes all the issues passed' do
- bulk_update(issues, state_event: 'close')
+ it 'updates the issuables milestone' do
+ bulk_update(issuables, milestone_id: milestone.id)
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
+ issuables.each do |issuable|
+ expect(issuable.reload.milestone).to eq(milestone)
+ end
end
+ end
- context 'when issue for a different project is created' do
- let(:private_project) { create(:project, :private) }
- let(:issue) { create(:issue, project: private_project, author: user) }
+ context 'with project issuables' do
+ describe 'close issues' do
+ let(:issues) { create_list(:issue, 2, project: project) }
- context 'when user has access to the project' do
- it 'closes all issues passed' do
- private_project.add_maintainer(user)
+ it 'succeeds and returns the correct number of issues updated' do
+ result = bulk_update(issues, state_event: 'close')
- bulk_update(issues + [issue], state_event: 'close')
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(issues.count)
+ end
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
- expect(private_project.issues.closed).not_to be_empty
- end
+ it 'closes all the issues passed' do
+ bulk_update(issues, state_event: 'close')
+
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
end
- context 'when user does not have access to project' do
- it 'only closes all issues that the user has access to' do
- bulk_update(issues + [issue], state_event: 'close')
+ context 'when issue for a different project is created' do
+ let(:private_project) { create(:project, :private) }
+ let(:issue) { create(:issue, project: private_project, author: user) }
+
+ context 'when user has access to the project' do
+ it 'closes all issues passed' do
+ private_project.add_maintainer(user)
+
+ bulk_update(issues + [issue], state_event: 'close')
+
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
+ expect(private_project.issues.closed).not_to be_empty
+ end
+ end
+
+ context 'when user does not have access to project' do
+ it 'only closes all issues that the user has access to' do
+ bulk_update(issues + [issue], state_event: 'close')
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
- expect(private_project.issues.closed).to be_empty
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
+ expect(private_project.issues.closed).to be_empty
+ end
end
end
end
- end
- describe 'reopen issues' do
- let(:issues) { create_list(:closed_issue, 2, project: project) }
+ describe 'reopen issues' do
+ let(:issues) { create_list(:closed_issue, 2, project: project) }
- it 'succeeds and returns the correct number of issues updated' do
- result = bulk_update(issues, state_event: 'reopen')
+ it 'succeeds and returns the correct number of issues updated' do
+ result = bulk_update(issues, state_event: 'reopen')
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
- end
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(issues.count)
+ end
- it 'reopens all the issues passed' do
- bulk_update(issues, state_event: 'reopen')
+ it 'reopens all the issues passed' do
+ bulk_update(issues, state_event: 'reopen')
- expect(project.issues.closed).to be_empty
- expect(project.issues.opened).not_to be_empty
+ expect(project.issues.closed).to be_empty
+ expect(project.issues.opened).not_to be_empty
+ end
end
- end
- describe 'updating merge request assignee' do
- let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) }
+ describe 'updating merge request assignee' do
+ let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) }
- context 'when the new assignee ID is a valid user' do
- it 'succeeds' do
- new_assignee = create(:user)
- project.add_developer(new_assignee)
+ context 'when the new assignee ID is a valid user' do
+ it 'succeeds' do
+ new_assignee = create(:user)
+ project.add_developer(new_assignee)
- result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
+ result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
- end
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+ end
- it 'updates the assignee to the user ID passed' do
- assignee = create(:user)
- project.add_developer(assignee)
+ it 'updates the assignee to the user ID passed' do
+ assignee = create(:user)
+ project.add_developer(assignee)
- expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
- .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
+ expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
+ .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
+ end
end
- end
- context "when the new assignee ID is #{IssuableFinder::NONE}" do
- it 'unassigns the issues' do
- expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
- .to change { merge_request.reload.assignee_ids }.to([])
+ context "when the new assignee ID is #{IssuableFinder::NONE}" do
+ it 'unassigns the issues' do
+ expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
+ .to change { merge_request.reload.assignee_ids }.to([])
+ end
end
- end
- context 'when the new assignee ID is not present' do
- it 'does not unassign' do
- expect { bulk_update(merge_request, assignee_ids: []) }
- .not_to change { merge_request.reload.assignee_ids }
+ context 'when the new assignee ID is not present' do
+ it 'does not unassign' do
+ expect { bulk_update(merge_request, assignee_ids: []) }
+ .not_to change { merge_request.reload.assignee_ids }
+ end
end
end
- end
- describe 'updating issue assignee' do
- let(:issue) { create(:issue, project: project, assignees: [user]) }
+ describe 'updating issue assignee' do
+ let(:issue) { create(:issue, project: project, assignees: [user]) }
- context 'when the new assignee ID is a valid user' do
- it 'succeeds' do
- new_assignee = create(:user)
- project.add_developer(new_assignee)
+ context 'when the new assignee ID is a valid user' do
+ it 'succeeds' do
+ new_assignee = create(:user)
+ project.add_developer(new_assignee)
- result = bulk_update(issue, assignee_ids: [new_assignee.id])
+ result = bulk_update(issue, assignee_ids: [new_assignee.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
- end
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+ end
- it 'updates the assignee to the user ID passed' do
- assignee = create(:user)
- project.add_developer(assignee)
- expect { bulk_update(issue, assignee_ids: [assignee.id]) }
- .to change { issue.reload.assignees.first }.from(user).to(assignee)
+ it 'updates the assignee to the user ID passed' do
+ assignee = create(:user)
+ project.add_developer(assignee)
+ expect { bulk_update(issue, assignee_ids: [assignee.id]) }
+ .to change { issue.reload.assignees.first }.from(user).to(assignee)
+ end
end
- end
- context "when the new assignee ID is #{IssuableFinder::NONE}" do
- it "unassigns the issues" do
- expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
- .to change { issue.reload.assignees.count }.from(1).to(0)
+ context "when the new assignee ID is #{IssuableFinder::NONE}" do
+ it "unassigns the issues" do
+ expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
+ .to change { issue.reload.assignees.count }.from(1).to(0)
+ end
end
- end
- context 'when the new assignee ID is not present' do
- it 'does not unassign' do
- expect { bulk_update(issue, assignee_ids: []) }
- .not_to change { issue.reload.assignees }
+ context 'when the new assignee ID is not present' do
+ it 'does not unassign' do
+ expect { bulk_update(issue, assignee_ids: []) }
+ .not_to change(issue.assignees, :count)
+ end
end
end
- end
- describe 'updating milestones' do
- let(:issue) { create(:issue, project: project) }
- let(:milestone) { create(:milestone, project: project) }
+ describe 'updating milestones' do
+ let(:issuables) { [create(:issue, project: project)] }
+ let(:milestone) { create(:milestone, project: project) }
- it 'succeeds' do
- result = bulk_update(issue, milestone_id: milestone.id)
-
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ it_behaves_like 'updates milestones'
end
- it 'updates the issue milestone' do
- expect { bulk_update(issue, milestone_id: milestone.id) }
- .to change { issue.reload.milestone }.from(nil).to(milestone)
- end
- end
-
- describe 'updating labels' do
- def create_issue_with_labels(labels)
- create(:labeled_issue, project: project, labels: labels)
- end
+ describe 'updating labels' do
+ def create_issue_with_labels(labels)
+ create(:labeled_issue, project: project, labels: labels)
+ end
- let(:bug) { create(:label, project: project) }
- let(:regression) { create(:label, project: project) }
- let(:merge_requests) { create(:label, project: project) }
-
- let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
- let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
- let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
- let(:issue_no_labels) { create(:issue, project: project) }
- let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
-
- let(:labels) { [] }
- let(:add_labels) { [] }
- let(:remove_labels) { [] }
-
- let(:bulk_update_params) do
- {
- label_ids: labels.map(&:id),
- add_label_ids: add_labels.map(&:id),
- remove_label_ids: remove_labels.map(&:id)
- }
- end
+ let(:bug) { create(:label, project: project) }
+ let(:regression) { create(:label, project: project) }
+ let(:merge_requests) { create(:label, project: project) }
- before do
- bulk_update(issues, bulk_update_params)
- end
+ let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
+ let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
+ let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
+ let(:issue_no_labels) { create(:issue, project: project) }
+ let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
- context 'when label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_no_labels] }
- let(:labels) { [bug, regression] }
+ let(:labels) { [] }
+ let(:add_labels) { [] }
+ let(:remove_labels) { [] }
- it 'updates the labels of all issues passed to the labels passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id)))
+ let(:bulk_update_params) do
+ {
+ label_ids: labels.map(&:id),
+ add_label_ids: add_labels.map(&:id),
+ remove_label_ids: remove_labels.map(&:id)
+ }
end
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ before do
+ bulk_update(issues, bulk_update_params)
end
- context 'when those label IDs are empty' do
- let(:labels) { [] }
+ context 'when label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_no_labels] }
+ let(:labels) { [bug, regression] }
- it 'updates the issues passed to have no labels' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ it 'updates the labels of all issues passed to the labels passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id)))
end
- end
- end
- context 'when add_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:add_labels) { [bug, regression, merge_requests] }
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
- it 'adds those label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
- end
+ context 'when those label IDs are empty' do
+ let(:labels) { [] }
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ it 'updates the issues passed to have no labels' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ end
+ end
end
- end
- context 'when remove_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:remove_labels) { [bug, regression, merge_requests] }
+ context 'when add_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:add_labels) { [bug, regression, merge_requests] }
- it 'removes those label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
- end
+ it 'adds those label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
+ end
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
end
- end
- context 'when add_label_ids and remove_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:add_labels) { [bug] }
- let(:remove_labels) { [merge_requests] }
+ context 'when remove_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:remove_labels) { [bug, regression, merge_requests] }
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
- end
+ it 'removes those label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ end
- it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
end
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
- end
- end
+ context 'when add_label_ids and remove_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:add_labels) { [bug] }
+ let(:remove_labels) { [merge_requests] }
- context 'when add_label_ids and label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
- let(:labels) { [merge_requests] }
- let(:add_labels) { [regression] }
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
- end
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id)
+ end
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
end
- it 'does not update issues not passed in' do
- expect(issue_no_labels.label_ids).to be_empty
- end
- end
+ context 'when add_label_ids and label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
+ let(:labels) { [merge_requests] }
+ let(:add_labels) { [regression] }
+
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
+ 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 'ignores the label IDs parameter' 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(regression.id)
+ it 'does not update issues not passed in' do
+ expect(issue_no_labels.label_ids).to be_empty
+ end
end
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ 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
- 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)
+ 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 '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] }
+ describe 'subscribe to issues' do
+ let(:issues) { create_list(:issue, 2, project: project) }
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
- end
+ it 'subscribes the given user' do
+ bulk_update(issues, subscription_event: 'subscribe')
- it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ expect(issues).to all(be_subscribed(user, project))
end
+ end
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+ describe 'unsubscribe from issues' do
+ let(:issues) do
+ create_list(:closed_issue, 2, project: project) do |issue|
+ issue.subscriptions.create(user: user, project: project, subscribed: true)
+ end
end
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ it 'unsubscribes the given user' do
+ bulk_update(issues, subscription_event: 'unsubscribe')
+
+ issues.each do |issue|
+ expect(issue).not_to be_subscribed(user, project)
+ end
end
end
end
- describe 'subscribe to issues' do
- let(:issues) { create_list(:issue, 2, project: project) }
+ context 'with group issuables ' do
+ let(:group) { create(:group) }
- it 'subscribes the given user' do
- bulk_update(issues, subscription_event: 'subscribe')
+ describe 'updating milestones' do
+ let(:milestone) { create(:milestone, group: group) }
+ let(:project) { create(:project, :repository, group: group) }
- expect(issues).to all(be_subscribed(user, project))
- end
- end
+ before do
+ group.add_maintainer(user)
+ end
+
+ context 'when issues' do
+ let(:issue1) { create(:issue, project: project) }
+ let(:issue2) { create(:issue, project: project) }
+ let(:issuables) { [issue1, issue2] }
- describe 'unsubscribe from issues' do
- let(:issues) do
- create_list(:closed_issue, 2, project: project) do |issue|
- issue.subscriptions.create(user: user, project: project, subscribed: true)
+ it_behaves_like 'updates milestones'
end
- end
- it 'unsubscribes the given user' do
- bulk_update(issues, subscription_event: 'unsubscribe')
+ 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] }
- issues.each do |issue|
- expect(issue).not_to be_subscribed(user, project)
+ it_behaves_like 'updates milestones'
end
end
end
diff --git a/spec/services/issuable/clone/content_rewriter_spec.rb b/spec/services/issuable/clone/content_rewriter_spec.rb
index 230e1123280..3479c20862a 100644
--- a/spec/services/issuable/clone/content_rewriter_spec.rb
+++ b/spec/services/issuable/clone/content_rewriter_spec.rb
@@ -165,5 +165,18 @@ describe Issuable::Clone::ContentRewriter do
expect(note.note_html).not_to eq(new_note.note_html)
end
end
+
+ context "discussion notes" do
+ let(:note) { create(:note, noteable: original_issue, note: "sample note", project: project1) }
+ let!(:discussion) { create(:discussion_note_on_issue, in_reply_to: note, note: "reply to sample note") }
+
+ it 'rewrites discussion correctly' do
+ subject.execute
+
+ expect(new_issue.notes.count).to eq(original_issue.notes.count)
+ expect(new_issue.notes.where(discussion_id: discussion.discussion_id).count).to eq(0)
+ expect(original_issue.notes.where(discussion_id: discussion.discussion_id).count).to eq(1)
+ end
+ end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 28fa5d12d9c..68b79132096 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -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
@@ -480,6 +480,22 @@ describe Issues::UpdateService, :mailer do
update_issue(description: "- [x] Task 1\n- [X] Task 2")
end
+ it 'does not check for spam on task status change' do
+ params = {
+ update_task: {
+ index: 1,
+ checked: false,
+ line_source: '- [x] Task 1',
+ line_number: 1
+ }
+ }
+ service = described_class.new(project, user, params)
+
+ expect(service).not_to receive(:spam_check)
+
+ service.execute(issue)
+ end
+
it 'creates system note about task status change' do
note1 = find_note('marked the task **Task 1** as completed')
note2 = find_note('marked the task **Task 2** as completed')
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 0933c6d4336..9e7a5260ca4 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -8,8 +8,8 @@ describe MergeRequests::GetUrlsService do
let(:project) { create(:project, :public, :repository) }
let(:service) { described_class.new(project) }
let(:source_branch) { "merge-test" }
- let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
- let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" }
+ let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" }
+ let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/#{merge_request.iid}" }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
@@ -119,7 +119,7 @@ describe MergeRequests::GetUrlsService do
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/markdown" }
let(:changes) { "#{new_branch_changes}\n#{existing_branch_changes}" }
- let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }
+ let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.full_path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" }
it 'returns 2 urls for both creating new and showing merge request' do
result = service.execute(changes)
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index aa759ac9edc..22578436c18 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -214,6 +214,19 @@ describe MergeRequests::MergeService do
allow(Rails.logger).to receive(:error)
end
+ context 'when source is missing' do
+ it 'logs and saves error' do
+ allow(merge_request).to receive(:diff_head_sha) { nil }
+
+ error_message = 'No source for merge'
+
+ service.execute(merge_request)
+
+ expect(merge_request.merge_error).to eq(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
+ end
+ end
+
it 'logs and saves error if there is an exception' do
error_message = 'error message'
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 14012b4ea2d..758679edc45 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -191,6 +191,19 @@ describe MergeRequests::MergeToRefService do
it { expect(todo).not_to be_done }
end
+ context 'when source is missing' do
+ it 'returns error' do
+ allow(merge_request).to receive(:diff_head_sha) { nil }
+
+ error_message = 'No source for merge'
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
+ end
+
context 'when target ref is passed as a parameter' do
let(:params) { { commit_message: 'merge train', target_ref: target_ref } }
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index 6efece64092..6e827f2ea5b 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -11,7 +11,11 @@ describe MergeRequests::MergeabilityCheckService do
end
it 'does not change the merge ref HEAD' do
- expect { subject }.not_to change(merge_request, :merge_ref_head)
+ merge_ref_head = merge_request.merge_ref_head
+
+ subject
+
+ expect(merge_request.reload.merge_ref_head).to eq merge_ref_head
end
it 'returns ServiceResponse.error' do
@@ -73,7 +77,7 @@ describe MergeRequests::MergeabilityCheckService do
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)
+ expect { subject }.not_to change(merge_request.merge_ref_head, :id)
end
end
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..ac40cf02c48 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -90,6 +90,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 +218,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 } }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index f566d235787..2e58da894e5 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -99,7 +99,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
@@ -598,7 +598,7 @@ describe MergeRequests::UpdateService, :mailer do
feature_visibility_attr = :"#{merge_request.model_name.plural}_access_level"
project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE)
- expect { update_merge_request(assignee_ids: [assignee]) }.not_to change { merge_request.reload.assignees }
+ expect { update_merge_request(assignee_ids: [assignee]) }.not_to change(merge_request.assignees, :count)
end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index f25e2fe5e2b..c20de1fd079 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -215,13 +215,14 @@ describe NotificationService, :mailer do
let(:project) { create(:project, :private) }
let(:issue) { create(:issue, project: project, assignees: [assignee]) }
let(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
- let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @unsubscribed_mentioned and @outsider also') }
+ let(:author) { create(:user) }
+ let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@mention referenced, @unsubscribed_mentioned and @outsider also') }
before do
- build_team(note.project)
+ build_team(project)
project.add_maintainer(issue.author)
project.add_maintainer(assignee)
- project.add_maintainer(note.author)
+ project.add_maintainer(author)
@u_custom_off = create_user_with_notification(:custom, 'custom_off')
project.add_guest(@u_custom_off)
@@ -240,7 +241,8 @@ describe NotificationService, :mailer do
describe '#new_note' do
it do
- add_users_with_subscription(note.project, issue)
+ add_users(project)
+ add_user_subscriptions(issue)
reset_delivered_emails!
expect(SentNotification).to receive(:record).with(issue, any_args).exactly(10).times
@@ -268,7 +270,8 @@ describe NotificationService, :mailer do
end
it "emails the note author if they've opted into notifications about their activity" do
- add_users_with_subscription(note.project, issue)
+ add_users(project)
+ add_user_subscriptions(issue)
reset_delivered_emails!
note.author.notified_of_own_activity = true
@@ -415,13 +418,15 @@ describe NotificationService, :mailer do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project, assignees: [assignee]) }
let(:mentioned_issue) { create(:issue, assignees: issue.assignees) }
- let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@all mentioned') }
+ let(:author) { create(:user) }
+ let(:note) { create(:note_on_issue, author: author, noteable: issue, project_id: issue.project_id, note: '@all mentioned') }
before do
- build_team(note.project)
- build_group(note.project)
- note.project.add_maintainer(note.author)
- add_users_with_subscription(note.project, issue)
+ build_team(project)
+ build_group(project)
+ add_users(project)
+ add_user_subscriptions(issue)
+ project.add_maintainer(author)
reset_delivered_emails!
end
@@ -473,17 +478,18 @@ describe NotificationService, :mailer do
context 'project snippet note' do
let!(:project) { create(:project, :public) }
let(:snippet) { create(:project_snippet, project: project, author: create(:user)) }
- let(:note) { create(:note_on_project_snippet, noteable: snippet, project_id: project.id, note: '@all mentioned') }
+ let(:author) { create(:user) }
+ let(:note) { create(:note_on_project_snippet, author: author, noteable: snippet, project_id: project.id, note: '@all mentioned') }
before do
build_team(project)
build_group(project)
+ project.add_maintainer(author)
# make sure these users can read the project snippet!
project.add_guest(@u_guest_watcher)
project.add_guest(@u_guest_custom)
add_member_for_parent_group(@pg_watcher, project)
- note.project.add_maintainer(note.author)
reset_delivered_emails!
end
@@ -708,10 +714,11 @@ describe NotificationService, :mailer do
let(:issue) { create :issue, project: project, assignees: [assignee], description: 'cc @participant @unsubscribed_mentioned' }
before do
- build_team(issue.project)
- build_group(issue.project)
+ build_team(project)
+ build_group(project)
- add_users_with_subscription(issue.project, issue)
+ add_users(project)
+ add_user_subscriptions(issue)
reset_delivered_emails!
update_custom_notification(:new_issue, @u_guest_custom, resource: project)
update_custom_notification(:new_issue, @u_custom_global)
@@ -1281,13 +1288,16 @@ describe NotificationService, :mailer do
let(:project) { create(:project, :public, :repository, namespace: group) }
let(:another_project) { create(:project, :public, namespace: group) }
let(:assignee) { create(:user) }
- let(:merge_request) { create :merge_request, source_project: project, assignees: [assignee], description: 'cc @participant' }
+ let(:assignees) { Array.wrap(assignee) }
+ let(:author) { create(:user) }
+ let(:merge_request) { create :merge_request, author: author, source_project: project, assignees: assignees, description: 'cc @participant' }
before do
- project.add_maintainer(merge_request.author)
- merge_request.assignees.each { |assignee| project.add_maintainer(assignee) }
- build_team(merge_request.target_project)
- add_users_with_subscription(merge_request.target_project, merge_request)
+ project.add_maintainer(author)
+ assignees.each { |assignee| project.add_maintainer(assignee) }
+ build_team(project)
+ add_users(project)
+ add_user_subscriptions(merge_request)
update_custom_notification(:new_merge_request, @u_guest_custom, resource: project)
update_custom_notification(:new_merge_request, @u_custom_global)
reset_delivered_emails!
@@ -1834,7 +1844,7 @@ describe NotificationService, :mailer do
describe 'ProjectMember' do
let(:project) { create(:project) }
- set(:added_user) { create(:user) }
+ let(:added_user) { create(:user) }
describe '#new_access_request' do
context 'for a project in a user namespace' do
@@ -2053,27 +2063,59 @@ 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
+
+ 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
+
+ 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
@@ -2417,7 +2459,7 @@ describe NotificationService, :mailer do
should_not_email(user, recipients: email_recipients)
end
- def add_users_with_subscription(project, issuable)
+ def add_users(project)
@subscriber = create :user
@unsubscriber = create :user
@unsubscribed_mentioned = create :user, username: 'unsubscribed_mentioned'
@@ -2429,7 +2471,9 @@ describe NotificationService, :mailer do
project.add_maintainer(@unsubscriber)
project.add_maintainer(@watcher_and_subscriber)
project.add_maintainer(@unsubscribed_mentioned)
+ end
+ def add_user_subscriptions(issuable)
issuable.subscriptions.create(user: @unsubscribed_mentioned, project: project, subscribed: false)
issuable.subscriptions.create(user: @subscriber, project: project, subscribed: true)
issuable.subscriptions.create(user: @subscribed_participant, project: project, subscribed: true)
diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
index 8d43ce4f662..af79a42b611 100644
--- a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
+++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb
@@ -12,6 +12,12 @@ describe PagesDomains::ObtainLetsEncryptCertificateService do
stub_lets_encrypt_settings
end
+ around do |example|
+ Sidekiq::Testing.fake! do
+ example.run
+ end
+ end
+
def expect_to_create_acme_challenge
expect(::PagesDomains::CreateAcmeOrderService).to receive(:new).with(pages_domain)
.and_wrap_original do |m, *args|
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 1dcfb739eb6..6bbaa410d56 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -347,13 +347,13 @@ describe Projects::UpdateService do
context 'when updating #pages_access_level' do
subject(:call_service) do
- update_project(project, admin, project_feature_attributes: { pages_access_level: ProjectFeature::PRIVATE })
+ update_project(project, admin, project_feature_attributes: { pages_access_level: ProjectFeature::ENABLED })
end
it 'updates the attribute' do
expect { call_service }
.to change { project.project_feature.pages_access_level }
- .to(ProjectFeature::PRIVATE)
+ .to(ProjectFeature::ENABLED)
end
it 'calls Projects::UpdatePagesConfigurationService' do
diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/services/self_monitoring/project/create_service_spec.rb
new file mode 100644
index 00000000000..d11e27c6d52
--- /dev/null
+++ b/spec/services/self_monitoring/project/create_service_spec.rb
@@ -0,0 +1,201 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SelfMonitoring::Project::CreateService do
+ describe '#execute' do
+ let(:result) { subject.execute }
+
+ let(:prometheus_settings) do
+ OpenStruct.new(
+ enable: true,
+ listen_address: 'localhost:9090'
+ )
+ end
+
+ before do
+ allow(Gitlab.config).to receive(:prometheus).and_return(prometheus_settings)
+ end
+
+ context 'without admin users' do
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq(
+ status: :error,
+ message: 'No active admin user found',
+ failed_step: :validate_admins
+ )
+ end
+ end
+
+ context 'with admin users' do
+ let(:project) { result[:project] }
+
+ 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)
+ )
+ end
+
+ shared_examples 'has prometheus service' do |listen_address|
+ it do
+ expect(result[:status]).to eq(:success)
+
+ prometheus = project.prometheus_service
+ expect(prometheus).not_to eq(nil)
+ expect(prometheus.api_url).to eq(listen_address)
+ expect(prometheus.active).to eq(true)
+ expect(prometheus.manual_configuration).to eq(true)
+ end
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'creates project with internal visibility' do
+ expect(result[:status]).to eq(:success)
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ it 'creates project with internal visibility even when internal visibility is restricted' do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+
+ expect(result[:status]).to eq(:success)
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ 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)
+ end
+
+ it 'adds all admins as maintainers' do
+ admin1 = create(:user, :admin)
+ admin2 = create(:user, :admin)
+ 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,
+ 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_behaves_like 'has prometheus service', 'http://localhost:9090'
+ # end
+
+ context 'with non default prometheus address' do
+ before do
+ prometheus_settings.listen_address = 'https://localhost:9090'
+ end
+
+ it_behaves_like 'has prometheus service', 'https://localhost:9090'
+ end
+
+ context 'when prometheus setting is not present in gitlab.yml' do
+ before do
+ allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
+ end
+
+ it 'does not fail' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus setting is disabled in gitlab.yml' do
+ before do
+ prometheus_settings.enable = false
+ end
+
+ it 'does not configure prometheus' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus listen address is blank in gitlab.yml' do
+ before do
+ prometheus_settings.listen_address = ''
+ end
+
+ it 'does not configure prometheus' do
+ expect(result).to include(status: :success)
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when project cannot be created' do
+ let(:project) { build(:project) }
+
+ before do
+ project.errors.add(:base, "Test error")
+
+ expect_next_instance_of(::Projects::CreateService) do |project_create_service|
+ expect(project_create_service).to receive(:execute)
+ .and_return(project)
+ end
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq({
+ status: :error,
+ message: 'Could not create project',
+ failed_step: :create_project
+ })
+ end
+ end
+
+ context 'when user cannot be added to project' do
+ before do
+ subject.instance_variable_set(:@instance_admins, [user, build(:user, :admin)])
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq({
+ status: :error,
+ message: 'Could not add admins as members',
+ failed_step: :add_project_members
+ })
+ end
+ end
+
+ context 'when prometheus manual configuration cannot be saved' do
+ before do
+ prometheus_settings.listen_address = 'httpinvalid://localhost:9090'
+ end
+
+ it 'returns error' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq(
+ status: :error,
+ message: 'Could not save prometheus manual configuration',
+ failed_step: :add_prometheus_manual_configuration
+ )
+ end
+ end
+ end
+ 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/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/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/spec_helper.rb b/spec/spec_helper.rb
index 62fdc039b5e..a44b5069ade 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -47,7 +47,7 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
quality_level = Quality::TestLevel.new
RSpec.configure do |config|
- config.use_transactional_fixtures = false
+ config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
config.fixture_path = Rails.root
@@ -103,6 +103,8 @@ RSpec.configure do |config|
config.include RedisHelpers
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.
@@ -133,6 +135,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)
@@ -262,10 +266,6 @@ RSpec.configure do |config|
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
@@ -289,6 +289,16 @@ RSpec.configure do |config|
config.before(:each, :https_pages_disabled) do |_|
allow(Gitlab.config.pages).to receive(:external_https).and_return(false)
end
+
+ # We can't use an `around` hook here because the wrapping transaction
+ # is not yet opened at the time that is triggered
+ config.prepend_before do
+ Gitlab::Database.set_open_transactions_baseline
+ end
+
+ config.append_after do
+ Gitlab::Database.reset_open_transactions_baseline
+ end
end
# add simpler way to match asset paths containing digest strings
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 56ac208a025..60990879fe2 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -58,6 +58,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/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 19b32c84d81..be1c2bc3046 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -50,7 +50,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 +80,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 +103,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 +121,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 +138,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 +146,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/database_cleaner.rb b/spec/support/database_cleaner.rb
index edd7de94203..f0dd6c52b74 100644
--- a/spec/support/database_cleaner.rb
+++ b/spec/support/database_cleaner.rb
@@ -26,31 +26,22 @@ RSpec.configure do |config|
end
config.append_after(:context) do
- DatabaseCleaner.clean_with(:deletion, cache_tables: false)
+ delete_from_all_tables!
end
- config.before do
- setup_database_cleaner
- DatabaseCleaner.strategy = :transaction
- end
+ config.around(:each, :delete) do |example|
+ self.class.use_transactional_tests = false
- config.before(:each, :js) do
- DatabaseCleaner.strategy = :deletion, { except: deletion_except_tables, cache_tables: false }
- end
+ example.run
- config.before(:each, :delete) do
- DatabaseCleaner.strategy = :deletion, { except: deletion_except_tables, cache_tables: false }
+ delete_from_all_tables!(except: deletion_except_tables)
end
- config.before(:each, :migration) do
- DatabaseCleaner.strategy = :deletion, { cache_tables: false }
- end
+ config.around(:each, :migration) do |example|
+ self.class.use_transactional_tests = false
- config.before do
- DatabaseCleaner.start
- end
+ example.run
- config.append_after do
- DatabaseCleaner.clean
+ delete_from_all_tables!
end
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index c69fa322073..08622dff6d9 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -1,4 +1,8 @@
module DbCleaner
+ def delete_from_all_tables!(except: nil)
+ DatabaseCleaner.clean_with(:deletion, cache_tables: false, except: except)
+ end
+
def deletion_except_tables
[]
end
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/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 1a09d48f4cd..ec3c460cd37 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -57,7 +57,8 @@ module GraphqlHelpers
end
def variables_for_mutation(name, input)
- graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h
+ graphql_input = prepare_input_for_mutation(input)
+
result = { input_variable_name_for_mutation(name) => graphql_input }
# Avoid trying to serialize multipart data into JSON
@@ -68,6 +69,18 @@ module GraphqlHelpers
end
end
+ # Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys
+ #
+ # prepare_input_for_mutation({ 'my_key' => 1 })
+ # => { 'myKey' => 1}
+ def prepare_input_for_mutation(input)
+ input.map do |name, value|
+ value = prepare_input_for_mutation(value) if value.is_a?(Hash)
+
+ [GraphqlHelpers.fieldnamerize(name), value]
+ end.to_h
+ end
+
def input_variable_name_for_mutation(mutation_name)
mutation_name = GraphqlHelpers.fieldnamerize(mutation_name)
mutation_field = GitlabSchema.mutation.fields[mutation_name]
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index cdd7724cc13..e9129bd263e 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -19,7 +19,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/key_generator_helper.rb b/spec/support/helpers/key_generator_helper.rb
index d55d8312c65..768b8c127b9 100644
--- a/spec/support/helpers/key_generator_helper.rb
+++ b/spec/support/helpers/key_generator_helper.rb
@@ -33,7 +33,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 3c7bcba2b42..278264f3df5 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -199,6 +199,11 @@ module KubernetesHelpers
.to_return(kube_response({}))
end
+ def stub_kubeclient_put_role(api_url, name, namespace: 'default')
+ WebMock.stub_request(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{name}")
+ .to_return(kube_response({}))
+ end
+
def kube_v1_secret_body(**options)
{
"kind" => "SecretList",
diff --git a/spec/support/helpers/memory_usage_helper.rb b/spec/support/helpers/memory_usage_helper.rb
new file mode 100644
index 00000000000..984ea8cc571
--- /dev/null
+++ b/spec/support/helpers/memory_usage_helper.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module MemoryUsageHelper
+ extend ActiveSupport::Concern
+
+ def gather_memory_data(csv_path)
+ write_csv_entry(csv_path,
+ {
+ example_group_path: TestEnv.topmost_example_group[:location],
+ example_group_description: TestEnv.topmost_example_group[:description],
+ time: Time.current,
+ job_name: ENV['CI_JOB_NAME']
+ }.merge(get_memory_usage))
+ end
+
+ def write_csv_entry(path, entry)
+ CSV.open(path, "a", headers: entry.keys, write_headers: !File.exist?(path)) do |file|
+ file << entry.values
+ end
+ end
+
+ def get_memory_usage
+ output, status = Gitlab::Popen.popen(%w(free -m))
+ abort "`free -m` return code is #{status}: #{output}" unless status.zero?
+
+ result = output.split("\n")[1].split(" ")[1..-1]
+ attrs = %i(m_total m_used m_free m_shared m_buffers_cache m_available).freeze
+
+ attrs.zip(result).to_h
+ end
+
+ included do |config|
+ config.after(:all) do
+ gather_memory_data(ENV['MEMORY_TEST_PATH']) if ENV['MEMORY_TEST_PATH']
+ end
+ end
+end
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index cc1a28cb264..272b24f7541 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -18,8 +18,12 @@ module MigrationsHelpers
ActiveRecord::Migrator.migrations_paths
end
+ def migration_context
+ ActiveRecord::MigrationContext.new(migrations_paths)
+ end
+
def migrations
- ActiveRecord::Migrator.migrations(migrations_paths)
+ migration_context.migrations
end
def clear_schema_cache!
@@ -96,8 +100,7 @@ module MigrationsHelpers
def schema_migrate_down!
disable_migrations_output do
- ActiveRecord::Migrator.migrate(migrations_paths,
- migration_schema_version)
+ migration_context.down(migration_schema_version)
end
reset_column_in_all_models
@@ -107,7 +110,7 @@ module MigrationsHelpers
reset_column_in_all_models
disable_migrations_output do
- ActiveRecord::Migrator.migrate(migrations_paths)
+ migration_context.up
end
reset_column_in_all_models
@@ -123,7 +126,7 @@ module MigrationsHelpers
end
def migrate!
- ActiveRecord::Migrator.up(migrations_paths) do |migration|
+ migration_context.up do |migration|
migration.name == described_class.name
end
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index c372a3f0e49..049702be1f6 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -65,6 +65,10 @@ module StubConfiguration
allow(Gitlab.config.artifacts).to receive_messages(to_settings(messages))
end
+ def stub_pages_setting(messages)
+ allow(Gitlab.config.pages).to receive_messages(to_settings(messages))
+ end
+
def stub_storage_settings(messages)
messages.deep_stringify_keys!
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index e63099d89b7..b062631b995 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -2,6 +2,7 @@ require 'rspec/mocks'
require 'toml-rb'
module TestEnv
+ extend ActiveSupport::Concern
extend self
ComponentFailedToInstallError = Class.new(StandardError)
@@ -108,6 +109,12 @@ module TestEnv
setup_forked_repo
end
+ included do |config|
+ config.append_before do
+ set_current_example_group
+ end
+ end
+
def disable_mailer
allow_any_instance_of(NotificationService).to receive(:mailer)
.and_return(double.as_null_object)
@@ -123,7 +130,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
@@ -134,14 +141,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,
@@ -297,11 +296,27 @@ module TestEnv
FileUtils.rm_rf(path)
end
+ def current_example_group
+ Thread.current[:current_example_group]
+ end
+
+ # looking for a top-level `describe`
+ def topmost_example_group
+ example_group = current_example_group
+ example_group = example_group[:parent_example_group] until example_group[:parent_example_group].nil?
+ example_group
+ end
+
private
+ def set_current_example_group
+ Thread.current[:current_example_group] = ::RSpec.current_example.metadata[:example_group]
+ end
+
# These are directories that should be preserved at cleanup time
def test_dirs
@test_dirs ||= %w[
+ frontend
gitaly
gitlab-shell
gitlab-test
@@ -346,10 +361,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/json_response.rb b/spec/support/json_response.rb
index 210b0e6d867..43d8ab73dde 100644
--- a/spec/support/json_response.rb
+++ b/spec/support/json_response.rb
@@ -1,5 +1,5 @@
RSpec.configure do |config|
- config.include_context 'JSON response'
+ config.include_context 'JSON response', type: :controller
config.include_context 'JSON response', type: :request
config.include_context 'JSON response', :api
end
diff --git a/spec/support/matchers/abort_matcher.rb b/spec/support/matchers/abort_matcher.rb
new file mode 100644
index 00000000000..ce1dd140210
--- /dev/null
+++ b/spec/support/matchers/abort_matcher.rb
@@ -0,0 +1,46 @@
+RSpec::Matchers.define :abort_execution do
+ match do |code_block|
+ @captured_stderr = StringIO.new
+ original_stderr = $stderr
+ $stderr = @captured_stderr
+
+ code_block.call
+
+ false
+ rescue SystemExit => e
+ captured = @captured_stderr.string.chomp
+ @actual_exit_code = e.status
+ break false unless e.status == 1
+
+ if @message
+ if @message.is_a? String
+ @message == captured
+ elsif @message.is_a? Regexp
+ @message.match?(captured)
+ else
+ raise ArgumentError, 'with_message must be either a String or a Regular Expression'
+ end
+ end
+
+ ensure
+ $stderr = original_stderr
+ end
+
+ chain :with_message do |message|
+ @message = message
+ end
+
+ failure_message do |block|
+ unless @actual_exit_code
+ break "expected #{block} to abort with '#{@message}' but didnt call abort."
+ end
+
+ if @actual_exit_code != 1
+ break "expected #{block} to abort with: '#{@message}' but exited with success instead."
+ end
+
+ "expected #{block} to abort with: '#{@message}' \n but received: '#{@captured_stderr.string.chomp}' instead."
+ end
+
+ supports_block_expectations
+end
diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb
index 9d806fc524d..4f5d53f9317 100644
--- a/spec/support/shared_contexts/email_shared_context.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
@@ -1,5 +1,3 @@
-require 'gitlab/email/receiver'
-
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/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index b4808ac0068..74389c4d82b 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[
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index ab0550e2613..68c2b6e10e2 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -720,6 +720,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/controllers/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index 0acc9e2a836..f4b02dc5350 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
@@ -46,7 +46,7 @@ shared_examples 'issuable notes filter' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
get :discussions, params: params
- discussions = JSON.parse(response.body)
+ discussions = json_response
expect(discussions.count).to eq(1)
expect(discussions.first["notes"].first["system"]).to be(false)
@@ -56,7 +56,7 @@ shared_examples 'issuable notes filter' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
get :discussions, params: params
- discussions = JSON.parse(response.body)
+ discussions = json_response
expect(discussions.count).to eq(1)
expect(discussions.first["notes"].first["system"]).to be(true)
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
new file mode 100644
index 00000000000..f2e1a95345b
--- /dev/null
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a Note mutation that does not create a Note' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Note.count }
+ end
+end
+
+RSpec.shared_examples 'a Note mutation that creates a Note' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { Note.count }.by(1)
+ end
+end
+
+RSpec.shared_examples 'a Note mutation when the user does not have permission' do
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+end
+
+RSpec.shared_examples 'a Note mutation when there are active record validation errors' do |model: Note|
+ before do
+ expect_next_instance_of(model) do |note|
+ expect(note).to receive(:valid?).at_least(:once).and_return(false)
+ expect(note).to receive_message_chain(
+ :errors,
+ :full_messages
+ ).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2']
+
+ it 'returns an empty Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('note')
+ expect(mutation_response['note']).to be_nil
+ end
+end
+
+RSpec.shared_examples 'a Note mutation when the given resource id is not for a Noteable' do
+ let(:noteable) { create(:label, project: project) }
+
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['Cannot add notes to this resource']
+end
+
+RSpec.shared_examples 'a Note mutation when the given resource id is not for a Note' do
+ let(:note) { create(:issue) }
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['Resource is not a note']
+end
diff --git a/spec/support/shared_examples/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb
index 1226841f24c..fea52c2eeb2 100644
--- a/spec/support/shared_examples/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/mentionable_shared_examples.rb
@@ -76,6 +76,30 @@ shared_examples 'a mentionable' do
expect(refs).to include(ext_commit)
end
+ context 'when there are cached markdown fields' do
+ before do
+ if subject.is_a?(CacheMarkdownField)
+ subject.refresh_markdown_cache
+ end
+ end
+
+ it 'sends in cached markdown fields when appropriate' do
+ if subject.is_a?(CacheMarkdownField)
+ expect_next_instance_of(Gitlab::ReferenceExtractor) do |ext|
+ attrs = subject.class.mentionable_attrs.collect(&:first) & subject.cached_markdown_fields.markdown_fields
+ attrs.each do |field|
+ expect(ext).to receive(:analyze).with(subject.send(field), hash_including(rendered: anything))
+ end
+ end
+
+ expect(subject).not_to receive(:refresh_markdown_cache)
+ expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original
+
+ subject.all_references(author)
+ end
+ end
+ end
+
it 'creates cross-reference notes' do
mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit,
ext_issue, ext_mr, ext_commit]
@@ -98,6 +122,33 @@ shared_examples 'an editable mentionable' do
[create(:issue, project: project), create(:issue, project: ext_proj)]
end
+ context 'when there are cached markdown fields' do
+ before do
+ if subject.is_a?(CacheMarkdownField)
+ subject.refresh_markdown_cache
+ end
+ end
+
+ it 'refreshes markdown cache if necessary' do
+ subject.save!
+
+ set_mentionable_text.call('This is a text')
+
+ if subject.is_a?(CacheMarkdownField)
+ expect_next_instance_of(Gitlab::ReferenceExtractor) do |ext|
+ subject.cached_markdown_fields.markdown_fields.each do |field|
+ expect(ext).to receive(:analyze).with(subject.send(field), hash_including(rendered: anything))
+ end
+ end
+
+ expect(subject).to receive(:refresh_markdown_cache)
+ expect(subject).to receive(:cached_markdown_fields).at_least(:once).and_call_original
+
+ subject.all_references(author)
+ end
+ end
+ end
+
it 'creates new cross-reference notes when the mentionable text is edited' do
subject.save
subject.create_cross_references!
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index e64c7e37a0c..4452b1c82cb 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -42,42 +42,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 '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)
+ context 'with group notification email' do
+ it 'is sent to user\'s group notification email' do
+ group_notification_email = 'user+group@example.com'
- expect(subject).to deliver_to(subgroup_notification_email)
- end
+ create(:notification_setting, user: recipient, source: project.group, notification_email: group_notification_email)
- 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 d99f94c76c3..4f9873d53e4 100644
--- a/spec/support/shared_examples/policies/clusterable_shared_examples.rb
+++ b/spec/support/shared_examples/policies/clusterable_shared_examples.rb
@@ -24,14 +24,6 @@ shared_examples 'clusterable policies' do
context 'with no clusters' do
it { expect_allowed(:add_cluster) }
end
-
- context 'with an existing cluster' do
- before do
- cluster
- end
-
- it { expect_disallowed(:add_cluster) }
- end
end
end
end
diff --git a/spec/support/shared_examples/taskable_shared_examples.rb b/spec/support/shared_examples/taskable_shared_examples.rb
index 4056ff06b84..4a1df1ce380 100644
--- a/spec/support/shared_examples/taskable_shared_examples.rb
+++ b/spec/support/shared_examples/taskable_shared_examples.rb
@@ -105,4 +105,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/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb
index 64568de424e..4cb6d001b9b 100644
--- a/spec/support/shared_examples/update_invalid_issuable.rb
+++ b/spec/support/shared_examples/update_invalid_issuable.rb
@@ -38,7 +38,7 @@ shared_examples 'update invalid issuable' do |klass|
put :update, params: params
expect(response.status).to eq(409)
- expect(JSON.parse(response.body)).to have_key('errors')
+ expect(json_response).to have_key('errors')
end
end
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/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb
index 4b04d9cec39..0e47408fc72 100644
--- a/spec/tasks/gitlab/storage_rake_spec.rb
+++ b/spec/tasks/gitlab/storage_rake_spec.rb
@@ -50,7 +50,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
expect(Project).not_to receive(:with_unmigrated_storage)
- expect { run_rake_task(task) }.to output(/This task requires database write access. Exiting./).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/This task requires database write access. Exiting./)
end
end
end
@@ -96,7 +96,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
expect(Project).not_to receive(:with_unmigrated_storage)
- expect { run_rake_task(task) }.to output(/There is already a rollback operation in progress/).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/There is already a rollback operation in progress/)
end
end
end
@@ -105,14 +105,23 @@ describe 'rake gitlab:storage:*', :sidekiq do
it 'does nothing' do
expect(::HashedStorage::MigratorWorker).not_to receive(:perform_async)
- run_rake_task(task)
+ expect { run_rake_task(task) }.to abort_execution.with_message('There are no projects requiring storage migration. Nothing to do!')
end
end
context 'with 3 legacy projects' do
let(:projects) { create_list(:project, 3, :legacy_storage) }
- it_behaves_like "handles custom BATCH env var", ::HashedStorage::MigratorWorker
+ it 'enqueues migrations and count projects correctly' do
+ projects.map(&:id).sort.tap do |ids|
+ stub_env('ID_FROM', ids[0])
+ stub_env('ID_TO', ids[1])
+ end
+
+ expect { run_rake_task(task) }.to output(/Enqueuing migration of 2 projects in batches/).to_stdout
+ end
+
+ it_behaves_like 'handles custom BATCH env var', ::HashedStorage::MigratorWorker
end
context 'with same id in range' do
@@ -120,7 +129,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
stub_env('ID_FROM', 99999)
stub_env('ID_TO', 99999)
- expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=99999/).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/There are no projects requiring storage migration with ID=99999/)
end
it 'displays a message when project exists but its already migrated' do
@@ -128,7 +137,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
stub_env('ID_FROM', project.id)
stub_env('ID_TO', project.id)
- expect { run_rake_task(task) }.to output(/There are no projects requiring storage migration with ID=#{project.id}/).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/There are no projects requiring storage migration with ID=#{project.id}/)
end
it 'enqueues migration when project can be found' do
@@ -153,7 +162,7 @@ describe 'rake gitlab:storage:*', :sidekiq do
expect(Project).not_to receive(:with_unmigrated_storage)
- expect { run_rake_task(task) }.to output(/There is already a migration operation in progress/).to_stderr
+ expect { run_rake_task(task) }.to abort_execution.with_message(/There is already a migration operation in progress/)
end
end
end
@@ -162,13 +171,22 @@ describe 'rake gitlab:storage:*', :sidekiq do
it 'does nothing' do
expect(::HashedStorage::RollbackerWorker).not_to receive(:perform_async)
- run_rake_task(task)
+ expect { run_rake_task(task) }.to abort_execution.with_message('There are no projects that can have storage rolledback. Nothing to do!')
end
end
context 'with 3 hashed projects' do
let(:projects) { create_list(:project, 3) }
+ it 'enqueues migrations and count projects correctly' do
+ projects.map(&:id).sort.tap do |ids|
+ stub_env('ID_FROM', ids[0])
+ stub_env('ID_TO', ids[1])
+ end
+
+ expect { run_rake_task(task) }.to output(/Enqueuing rollback of 2 projects in batches/).to_stdout
+ end
+
it_behaves_like "handles custom BATCH env var", ::HashedStorage::RollbackerWorker
end
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..a96b00bfd1d
--- /dev/null
+++ b/spec/validators/qualified_domain_array_validator_spec.rb
@@ -0,0 +1,111 @@
+# 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 'cannot be blank' do
+ it 'returns error when attribute is blank' do
+ record.domain_array = []
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq 'entries cannot be blank'
+ end
+ end
+
+ 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
+
+ describe 'validations' do
+ let(:validator) { described_class.new(attributes: [:domain_array]) }
+
+ it_behaves_like 'cannot be blank'
+
+ it 'returns error when attribute is nil' do
+ record.domain_array = nil
+
+ subject
+
+ expect(record.errors).to be_present
+ 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 'cannot 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 'allows when attribute is blank' do
+ record.domain_array = []
+
+ subject
+
+ expect(record.errors).to be_empty
+ end
+ end
+end
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/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
index 623237b111a..bf633a118ca 100644
--- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'notify/pipeline_failed_email.html.haml' do
expect(rendered).to have_content "Your pipeline has failed"
expect(rendered).to have_content pipeline.project.name
- expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' ')
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ')
expect(rendered).to have_content pipeline.commit.author_name
expect(rendered).to have_content "##{pipeline.id}"
expect(rendered).to have_content pipeline.user.name
@@ -45,7 +45,7 @@ describe 'notify/pipeline_failed_email.html.haml' do
expect(rendered).to have_content "Your pipeline has failed"
expect(rendered).to have_content pipeline.project.name
- expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' ')
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ')
expect(rendered).to have_content pipeline.commit.author_name
expect(rendered).to have_content "##{pipeline.id}"
expect(rendered).to have_content "by API"
diff --git a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
index 81245239eba..060274eb56a 100644
--- a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
+++ b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb
@@ -30,7 +30,7 @@ describe 'notify/pipeline_failed_email.text.erb' do
expect(rendered).to have_content('Your pipeline has failed')
expect(rendered).to have_content(pipeline.project.name)
- expect(rendered).to have_content(pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' '))
+ expect(rendered).to have_content(pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' '))
expect(rendered).to have_content(pipeline.commit.author_name)
expect(rendered).to have_content("##{pipeline.id}")
expect(rendered).to have_content(pipeline.user.name)
diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
index a876bf13e11..46a6c908863 100644
--- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb
+++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'notify/pipeline_success_email.html.haml' do
expect(rendered).to have_content "Your pipeline has passed"
expect(rendered).to have_content pipeline.project.name
- expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' ')
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ')
expect(rendered).to have_content pipeline.commit.author_name
expect(rendered).to have_content "##{pipeline.id}"
expect(rendered).to have_content pipeline.user.name
@@ -45,7 +45,7 @@ describe 'notify/pipeline_success_email.html.haml' do
expect(rendered).to have_content "Your pipeline has passed"
expect(rendered).to have_content pipeline.project.name
- expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub!(/\s+/, ' ')
+ expect(rendered).to have_content pipeline.git_commit_message.truncate(50).gsub(/\s+/, ' ')
expect(rendered).to have_content pipeline.commit.author_name
expect(rendered).to have_content "##{pipeline.id}"
expect(rendered).to have_content "by API"
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/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/yarn.lock b/yarn.lock
index 2cacc5399f4..0936e14c0a5 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.4.4":
+ 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"
@@ -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,24 +205,24 @@
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"
@@ -225,10 +233,15 @@
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.4.4":
+ 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-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-json-strings@^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:
+ "@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.13.4"
+ 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.4.4":
+ 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,13 +882,37 @@
"@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"
@@ -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"
@@ -705,21 +996,21 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.67.0.tgz#c7b94eca13b99fd3aaa737fb6dcc0abc41d3c579"
integrity sha512-hJOmWEs6RkjzyKkb1vc9wwKGZIBIP0coHkxu/KgOoxhBVudpGk4CH7xJ6UuB2TKpb0SEh5CC1CzRZfBYaFhsaA==
-"@gitlab/ui@^5.3.2":
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.3.2.tgz#8ee906cf0586834de0077e165f25764c0bf8a9e9"
- integrity sha512-VgxlDXqG2q+u72Km+/Ljdvjh0DzvljvsztiXTxnOO+Eb+/I26JBWfdboqFr3E02JzT8W4s4rRinhRttLWfcM/A==
+"@gitlab/ui@^5.7.1":
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.7.1.tgz#e55d04052dd6e50ed1e90676aacc64290d62c0b6"
+ integrity sha512-F06/6z6/69LbKIK0PYRDTB/teSPUnF7LijHl4JiuYHXn7Y2/iVoLsAMikhT89RVR84orHPGnw16vtCPjSjBDrA==
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.26"
copy-to-clipboard "^3.0.8"
core-js "^2.6.9"
echarts "^4.2.0-rc.2"
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 +1236,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 +1307,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 +1530,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 +1574,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 +1604,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 +1626,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 +1675,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"
@@ -1473,6 +1784,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"
@@ -1657,17 +1975,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 +2062,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 +2098,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"
@@ -1909,6 +2233,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 +2255,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.26:
+ version "2.0.0-rc.26"
+ resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.26.tgz#2b8b9116a452584ae20ba8310d143635bc9e1375"
+ integrity sha512-AzN+IRTmfR9rLFWNGt+v2XPmQjZiAlH4x5z2kStA3UoJ5LR91K34iZ8apaa6isDo+DfWDcwH4q7OwHM+VNxWwg==
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"
@@ -1982,6 +2315,13 @@ braces@^2.3.0, braces@^2.3.1:
split-string "^3.0.2"
to-regex "^3.0.1"
+braces@^3.0.1:
+ 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 +2332,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 +2397,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 +2420,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 +2435,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,6 +2459,11 @@ 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"
@@ -2173,6 +2528,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 +2624,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 +2669,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==
@@ -2470,16 +2830,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 +2862,17 @@ 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-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 "^1.0.0"
- is-supported-regexp-flag "^1.0.0"
+ is-regexp "^2.0.0"
clone-response@1.0.2:
version "1.0.2"
@@ -2527,6 +2881,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 +2929,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,7 +2959,12 @@ 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==
@@ -2605,6 +2983,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"
@@ -2677,7 +3060,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 +3070,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 +3158,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,27 +3204,21 @@ 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==
+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.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==
+ browserslist "^4.6.2"
+ core-js-pure "3.1.4"
+ semver "^6.1.1"
-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-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@>=2.6.5 <3.0.0", core-js@^2.2.0, core-js@^2.6.9:
+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==
@@ -2847,14 +3238,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 +3404,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"
@@ -3432,7 +3828,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 +3931,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 +3999,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 +4026,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 +4049,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 +4063,7 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
-dir-glob@^2.2.1:
+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 +4095,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 +4124,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 +4262,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 +4279,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"
@@ -3825,10 +4331,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 +4349,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 +4468,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"
@@ -4230,7 +4754,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,12 +4838,12 @@ 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"
@@ -4479,6 +5003,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 +5052,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 +5093,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 +5131,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"
@@ -4693,13 +5234,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"
@@ -4776,6 +5317,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"
@@ -4850,6 +5399,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 +5414,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 +5465,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,11 +5502,32 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
+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"
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
+glob-to-regexp@^0.4.0:
+ version "0.4.1"
+ 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:
version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
@@ -4979,6 +5588,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"
@@ -5007,13 +5621,14 @@ 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@^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"
@@ -5088,10 +5703,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"
@@ -5117,6 +5732,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"
@@ -5266,12 +5891,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==
@@ -5337,10 +6000,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,10 +6125,10 @@ ignore@^4.0.3, ignore@^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"
@@ -5485,10 +6153,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"
@@ -5556,7 +6224,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==
@@ -5633,6 +6301,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"
@@ -5677,7 +6353,7 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
-is-buffer@^1.1.5, is-buffer@~1.1.1:
+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==
@@ -5789,6 +6465,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"
@@ -5821,6 +6502,11 @@ 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"
@@ -5838,6 +6524,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"
@@ -5901,6 +6592,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"
@@ -5911,16 +6614,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"
@@ -5933,11 +6638,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"
@@ -6535,7 +7252,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,6 +7384,11 @@ json5@^0.5.0:
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
+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"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -6837,10 +7559,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"
@@ -6849,6 +7571,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"
@@ -6863,6 +7592,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"
@@ -6873,6 +7609,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"
@@ -6900,6 +7641,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"
@@ -6992,9 +7738,9 @@ lodash.kebabcase@4.1.1:
integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY=
lodash.mergewith@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55"
- integrity sha1-FQzwoWeR9ZA7iJHqsVRgknS96lU=
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
+ integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
lodash.snakecase@4.1.1:
version "4.1.1"
@@ -7011,18 +7757,25 @@ 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.11"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
- integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
+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.5.0, 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"
+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:
+ chalk "^2.4.2"
+
log4js@^3.0.0:
version "3.0.5"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-3.0.5.tgz#b80146bfebad68b430d4f3569556d8a6edfef303"
@@ -7136,7 +7889,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=
@@ -7151,6 +7904,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"
@@ -7184,10 +7942,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"
@@ -7213,6 +7971,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"
@@ -7223,13 +8027,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"
@@ -7307,15 +8104,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"
@@ -7327,7 +8127,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==
@@ -7346,6 +8146,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"
@@ -7371,10 +8179,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:
+ 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"
@@ -7439,7 +8247,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=
@@ -7476,9 +8284,9 @@ mississippi@^3.0.0:
through2 "^2.0.0"
mixin-deep@^1.2.0:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
- integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
+ integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
dependencies:
for-in "^1.0.2"
is-extendable "^1.0.1"
@@ -7490,6 +8298,26 @@ 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"
@@ -7714,10 +8542,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"
@@ -7823,6 +8651,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"
@@ -7909,7 +8754,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==
@@ -7961,7 +8806,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=
@@ -8014,6 +8859,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"
@@ -8043,15 +8895,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"
@@ -8215,6 +9058,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"
@@ -8230,11 +9089,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"
@@ -8311,6 +9193,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"
@@ -8363,6 +9257,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.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"
@@ -8476,17 +9375,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"
@@ -8534,7 +9433,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==
@@ -8549,7 +9448,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==
@@ -8589,6 +9488,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"
@@ -8599,6 +9507,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"
@@ -8608,10 +9521,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"
@@ -8622,7 +9535,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=
@@ -8664,16 +9577,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"
@@ -8697,6 +9610,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"
@@ -8818,6 +9738,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"
@@ -8873,12 +9798,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"
@@ -8907,11 +9832,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"
@@ -8983,6 +9916,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"
@@ -9065,7 +10006,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.0, 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==
@@ -9087,7 +10028,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=
@@ -9099,6 +10040,27 @@ 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"
+ integrity sha512-JQD83C9gAs5B5j2j40qLn/K83HhR8po3bUonebNeuJQUZbbn7q1HxL9kQuPBtxoXkaUpbtEmpFBw5kzyYnnJDA==
+ dependencies:
+ call-me-maybe "^1.0.1"
+ glob-to-regexp "^0.4.0"
+
readdirp@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
@@ -9144,10 +10106,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"
@@ -9159,14 +10121,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"
@@ -9238,6 +10196,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"
@@ -9259,6 +10248,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"
@@ -9279,6 +10304,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"
@@ -9288,6 +10321,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"
@@ -9303,7 +10369,7 @@ repeat-string@^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=
@@ -9315,7 +10381,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=
@@ -9422,10 +10488,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"
@@ -9437,10 +10510,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"
@@ -9530,6 +10603,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"
@@ -9653,10 +10731,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"
@@ -9808,6 +10886,11 @@ slash@^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"
@@ -9927,6 +11010,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"
@@ -9990,6 +11080,11 @@ 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==
+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"
@@ -10127,6 +11222,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"
@@ -10135,6 +11237,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"
@@ -10182,6 +11292,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"
@@ -10208,6 +11323,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"
@@ -10215,11 +11344,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"
@@ -10244,7 +11368,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==
@@ -10305,79 +11429,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"
@@ -10398,7 +11525,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==
@@ -10425,13 +11552,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"
@@ -10556,15 +11683,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=
@@ -10574,6 +11709,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"
@@ -10598,6 +11738,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"
@@ -10661,6 +11813,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"
@@ -10691,6 +11851,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"
@@ -10701,6 +11868,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"
@@ -10728,6 +11902,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"
@@ -10846,7 +12025,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=
@@ -10874,6 +12053,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"
@@ -10934,6 +12118,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"
@@ -10977,6 +12173,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"
@@ -10984,6 +12188,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"
@@ -10991,11 +12202,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"
@@ -11008,6 +12229,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"
@@ -11015,10 +12243,10 @@ 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"
@@ -11183,6 +12411,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"
@@ -11209,6 +12442,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"
@@ -11219,6 +12492,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"
@@ -11309,7 +12641,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==
@@ -11377,12 +12709,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"
@@ -11714,10 +13047,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"
@@ -11766,13 +13099,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"
@@ -11791,24 +13117,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"