summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintignore1
-rw-r--r--.eslintrc.yml3
-rw-r--r--.gitlab-ci.yml16
-rw-r--r--.gitlab/issue_templates/Documentation.md2
-rw-r--r--.gitlab/merge_request_templates/Security Release.md8
-rw-r--r--.rubocop.yml14
-rw-r--r--.scss-lint.yml4
-rw-r--r--.stylelintrc10
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock14
-rw-r--r--Pipfile12
-rw-r--r--Pipfile.lock30
-rw-r--r--app/assets/images/select2-spinner.gifbin0 -> 1849 bytes
-rw-r--r--app/assets/images/select2.pngbin0 -> 613 bytes
-rw-r--r--app/assets/images/select2x2.pngbin0 -> 845 bytes
-rw-r--r--app/assets/javascripts/behaviors/markdown/copy_as_gfm.js6
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js2
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js4
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue7
-rw-r--r--app/assets/javascripts/commons/bootstrap.js60
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue30
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue35
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue54
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue29
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue2
-rw-r--r--app/assets/javascripts/error_tracking/store/actions.js2
-rw-r--r--app/assets/javascripts/groups/components/app.vue2
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue2
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue2
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js25
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue10
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue86
-rw-r--r--app/assets/javascripts/monitoring/constants.js1
-rw-r--r--app/assets/javascripts/mr_popover/components/mr_popover.vue110
-rw-r--r--app/assets/javascripts/mr_popover/constants.js10
-rw-r--r--app/assets/javascripts/mr_popover/index.js62
-rw-r--r--app/assets/javascripts/mr_popover/queries/merge_request.graphql14
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue29
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue8
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue1
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue26
-rw-r--r--app/assets/javascripts/notifications_dropdown.js1
-rw-r--r--app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js10
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js9
-rw-r--r--app/assets/javascripts/pipelines/stores/pipeline_store.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/svg_gradient.vue6
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss10
-rw-r--r--app/assets/stylesheets/components/popover.scss7
-rw-r--r--app/assets/stylesheets/components/related_items_list.scss1
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/common.scss14
-rw-r--r--app/assets/stylesheets/framework/files.scss6
-rw-r--r--app/assets/stylesheets/framework/forms.scss2
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss28
-rw-r--r--app/assets/stylesheets/framework/mixins.scss34
-rw-r--r--app/assets/stylesheets/framework/page_title.scss1
-rw-r--r--app/assets/stylesheets/framework/sortable.scss92
-rw-r--r--app/assets/stylesheets/framework/typography.scss118
-rw-r--r--app/assets/stylesheets/framework/variables.scss8
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss1
-rw-r--r--app/assets/stylesheets/framework/wells.scss4
-rw-r--r--app/assets/stylesheets/notify.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss17
-rw-r--r--app/assets/stylesheets/pages/diff.scss254
-rw-r--r--app/assets/stylesheets/pages/environments.scss4
-rw-r--r--app/assets/stylesheets/pages/help.scss8
-rw-r--r--app/assets/stylesheets/pages/issuable.scss16
-rw-r--r--app/assets/stylesheets/pages/issues.scss5
-rw-r--r--app/assets/stylesheets/pages/members.scss49
-rw-r--r--app/assets/stylesheets/pages/milestone.scss1
-rw-r--r--app/assets/stylesheets/pages/notes.scss4
-rw-r--r--app/assets/stylesheets/pages/profile.scss48
-rw-r--r--app/assets/stylesheets/pages/wiki.scss6
-rw-r--r--app/assets/stylesheets/print.scss18
-rw-r--r--app/controllers/clusters/clusters_controller.rb10
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb6
-rw-r--r--app/controllers/concerns/creates_commit.rb25
-rw-r--r--app/controllers/concerns/lfs_request.rb6
-rw-r--r--app/controllers/concerns/membership_actions.rb35
-rw-r--r--app/controllers/concerns/spammable_actions.rb6
-rw-r--r--app/controllers/concerns/uploads_actions.rb4
-rw-r--r--app/controllers/dashboard/todos_controller.rb4
-rw-r--r--app/controllers/graphql_controller.rb5
-rw-r--r--app/controllers/groups/runners_controller.rb10
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_server_controller.rb12
-rw-r--r--app/controllers/import/fogbugz_controller.rb6
-rw-r--r--app/controllers/import/gitea_controller.rb2
-rw-r--r--app/controllers/import/gitlab_controller.rb2
-rw-r--r--app/controllers/import/gitlab_projects_controller.rb4
-rw-r--r--app/controllers/import/google_code_controller.rb12
-rw-r--r--app/controllers/projects/blob_controller.rb7
-rw-r--r--app/controllers/projects/branches_controller.rb4
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/controllers/projects/git_http_client_controller.rb14
-rw-r--r--app/controllers/projects/git_http_controller.rb4
-rw-r--r--app/controllers/projects/group_links_controller.rb2
-rw-r--r--app/controllers/projects/hooks_controller.rb2
-rw-r--r--app/controllers/projects/imports_controller.rb4
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/controllers/projects/jobs_controller.rb2
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/lfs_api_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests/conflicts_controller.rb8
-rw-r--r--app/controllers/projects/merge_requests_controller.rb8
-rw-r--r--app/controllers/projects/mirrors_controller.rb4
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb8
-rw-r--r--app/controllers/projects/runners_controller.rb10
-rw-r--r--app/controllers/projects/services_controller.rb10
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb6
-rw-r--r--app/controllers/projects/tree_controller.rb2
-rw-r--r--app/controllers/projects/triggers_controller.rb14
-rw-r--r--app/controllers/projects/wikis_controller.rb12
-rw-r--r--app/controllers/search_controller.rb2
-rw-r--r--app/finders/groups_finder.rb8
-rw-r--r--app/finders/projects_finder.rb2
-rw-r--r--app/graphql/types/ci/detailed_status_type.rb17
-rw-r--r--app/graphql/types/ci/pipeline_type.rb4
-rw-r--r--app/helpers/groups_helper.rb5
-rw-r--r--app/helpers/search_helper.rb11
-rw-r--r--app/models/abuse_report.rb2
-rw-r--r--app/models/appearance.rb2
-rw-r--r--app/models/application_record.rb8
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/application_setting/term.rb2
-rw-r--r--app/models/audit_event.rb2
-rw-r--r--app/models/award_emoji.rb2
-rw-r--r--app/models/badge.rb2
-rw-r--r--app/models/board.rb2
-rw-r--r--app/models/board_group_recent_visit.rb2
-rw-r--r--app/models/board_project_recent_visit.rb2
-rw-r--r--app/models/broadcast_message.rb2
-rw-r--r--app/models/chat_name.rb2
-rw-r--r--app/models/chat_team.rb2
-rw-r--r--app/models/ci/build.rb6
-rw-r--r--app/models/ci/build_metadata.rb2
-rw-r--r--app/models/ci/build_runner_session.rb2
-rw-r--r--app/models/ci/build_trace_chunk.rb2
-rw-r--r--app/models/ci/build_trace_section.rb2
-rw-r--r--app/models/ci/build_trace_section_name.rb2
-rw-r--r--app/models/ci/group_variable.rb2
-rw-r--r--app/models/ci/job_artifact.rb2
-rw-r--r--app/models/ci/pipeline.rb38
-rw-r--r--app/models/ci/pipeline_chat_data.rb2
-rw-r--r--app/models/ci/pipeline_schedule.rb2
-rw-r--r--app/models/ci/pipeline_schedule_variable.rb2
-rw-r--r--app/models/ci/pipeline_variable.rb2
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/ci/runner_namespace.rb2
-rw-r--r--app/models/ci/runner_project.rb2
-rw-r--r--app/models/ci/stage.rb2
-rw-r--r--app/models/ci/trigger.rb2
-rw-r--r--app/models/ci/trigger_request.rb2
-rw-r--r--app/models/ci/variable.rb2
-rw-r--r--app/models/clusters/applications/cert_manager.rb2
-rw-r--r--app/models/clusters/applications/helm.rb2
-rw-r--r--app/models/clusters/applications/ingress.rb2
-rw-r--r--app/models/clusters/applications/jupyter.rb2
-rw-r--r--app/models/clusters/applications/knative.rb6
-rw-r--r--app/models/clusters/applications/prometheus.rb2
-rw-r--r--app/models/clusters/applications/runner.rb17
-rw-r--r--app/models/clusters/cluster.rb19
-rw-r--r--app/models/clusters/group.rb2
-rw-r--r--app/models/clusters/kubernetes_namespace.rb2
-rw-r--r--app/models/clusters/platforms/kubernetes.rb6
-rw-r--r--app/models/clusters/project.rb2
-rw-r--r--app/models/clusters/providers/gcp.rb2
-rw-r--r--app/models/commit_collection.rb35
-rw-r--r--app/models/commit_status.rb2
-rw-r--r--app/models/concerns/atomic_internal_id.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb2
-rw-r--r--app/models/concerns/ignorable_column.rb2
-rw-r--r--app/models/concerns/participable.rb2
-rw-r--r--app/models/concerns/reactive_caching.rb2
-rw-r--r--app/models/concerns/sha_attribute.rb2
-rw-r--r--app/models/concerns/strip_attribute.rb2
-rw-r--r--app/models/container_repository.rb2
-rw-r--r--app/models/conversational_development_index/metric.rb2
-rw-r--r--app/models/deploy_keys_project.rb2
-rw-r--r--app/models/deploy_token.rb2
-rw-r--r--app/models/deployment.rb2
-rw-r--r--app/models/diff_note.rb4
-rw-r--r--app/models/email.rb2
-rw-r--r--app/models/environment.rb2
-rw-r--r--app/models/epic.rb2
-rw-r--r--app/models/error_tracking/project_error_tracking_setting.rb9
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/fork_network.rb2
-rw-r--r--app/models/fork_network_member.rb2
-rw-r--r--app/models/gpg_key.rb2
-rw-r--r--app/models/gpg_key_subkey.rb2
-rw-r--r--app/models/group_custom_attribute.rb2
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/models/hooks/web_hook_log.rb2
-rw-r--r--app/models/identity.rb2
-rw-r--r--app/models/identity/uniqueness_scopes.rb2
-rw-r--r--app/models/import_export_upload.rb2
-rw-r--r--app/models/internal_id.rb2
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/issue/metrics.rb2
-rw-r--r--app/models/issue_assignee.rb2
-rw-r--r--app/models/key.rb2
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/label_link.rb2
-rw-r--r--app/models/label_priority.rb2
-rw-r--r--app/models/lfs_file_lock.rb2
-rw-r--r--app/models/lfs_object.rb2
-rw-r--r--app/models/lfs_objects_project.rb2
-rw-r--r--app/models/list.rb2
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/members/group_member.rb2
-rw-r--r--app/models/merge_request.rb27
-rw-r--r--app/models/merge_request/metrics.rb2
-rw-r--r--app/models/merge_request_assignee.rb6
-rw-r--r--app/models/merge_request_diff.rb174
-rw-r--r--app/models/merge_request_diff_commit.rb2
-rw-r--r--app/models/merge_request_diff_file.rb2
-rw-r--r--app/models/merge_requests_closing_issues.rb2
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/note_diff_file.rb2
-rw-r--r--app/models/notification_setting.rb2
-rw-r--r--app/models/pages_domain.rb2
-rw-r--r--app/models/personal_access_token.rb2
-rw-r--r--app/models/pool_repository.rb2
-rw-r--r--app/models/postgresql/replication_slot.rb2
-rw-r--r--app/models/programming_language.rb2
-rw-r--r--app/models/project.rb55
-rw-r--r--app/models/project_authorization.rb2
-rw-r--r--app/models/project_auto_devops.rb2
-rw-r--r--app/models/project_ci_cd_setting.rb2
-rw-r--r--app/models/project_custom_attribute.rb2
-rw-r--r--app/models/project_daily_statistic.rb2
-rw-r--r--app/models/project_deploy_token.rb2
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/models/project_group_link.rb2
-rw-r--r--app/models/project_import_data.rb2
-rw-r--r--app/models/project_import_state.rb2
-rw-r--r--app/models/project_repository.rb2
-rw-r--r--app/models/project_statistics.rb2
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/prometheus_metric.rb2
-rw-r--r--app/models/protected_branch.rb2
-rw-r--r--app/models/protected_branch/merge_access_level.rb2
-rw-r--r--app/models/protected_branch/push_access_level.rb2
-rw-r--r--app/models/protected_tag.rb2
-rw-r--r--app/models/protected_tag/create_access_level.rb2
-rw-r--r--app/models/push_event_payload.rb2
-rw-r--r--app/models/redirect_route.rb2
-rw-r--r--app/models/release.rb2
-rw-r--r--app/models/releases/link.rb2
-rw-r--r--app/models/remote_mirror.rb2
-rw-r--r--app/models/repository.rb8
-rw-r--r--app/models/repository_language.rb2
-rw-r--r--app/models/resource_label_event.rb2
-rw-r--r--app/models/route.rb2
-rw-r--r--app/models/sent_notification.rb2
-rw-r--r--app/models/service.rb2
-rw-r--r--app/models/shard.rb2
-rw-r--r--app/models/snippet.rb2
-rw-r--r--app/models/spam_log.rb2
-rw-r--r--app/models/subscription.rb2
-rw-r--r--app/models/suggestion.rb46
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/term_agreement.rb2
-rw-r--r--app/models/timelog.rb2
-rw-r--r--app/models/todo.rb2
-rw-r--r--app/models/trending_project.rb2
-rw-r--r--app/models/u2f_registration.rb2
-rw-r--r--app/models/upload.rb2
-rw-r--r--app/models/user.rb2
-rw-r--r--app/models/user_agent_detail.rb2
-rw-r--r--app/models/user_callout.rb2
-rw-r--r--app/models/user_custom_attribute.rb2
-rw-r--r--app/models/user_interacted_project.rb2
-rw-r--r--app/models/user_preference.rb2
-rw-r--r--app/models/user_status.rb2
-rw-r--r--app/models/user_synced_attributes_metadata.rb2
-rw-r--r--app/models/users_star_project.rb2
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/presenters/ci/build_runner_presenter.rb9
-rw-r--r--app/presenters/clusters/cluster_presenter.rb4
-rw-r--r--app/serializers/diff_file_entity.rb2
-rw-r--r--app/services/after_branch_delete_service.rb5
-rw-r--r--app/services/ci/destroy_pipeline_service.rb2
-rw-r--r--app/services/clusters/applications/base_service.rb24
-rw-r--r--app/services/clusters/applications/create_service.rb22
-rw-r--r--app/services/clusters/applications/update_service.rb21
-rw-r--r--app/services/concerns/suggestible.rb30
-rw-r--r--app/services/concerns/users/participable_service.rb26
-rw-r--r--app/services/error_tracking/list_issues_service.rb11
-rw-r--r--app/services/git/branch_push_service.rb244
-rw-r--r--app/services/git/tag_push_service.rb68
-rw-r--r--app/services/git_push_service.rb240
-rw-r--r--app/services/git_tag_push_service.rb66
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/merge_requests/base_service.rb26
-rw-r--r--app/services/merge_requests/create_service.rb2
-rw-r--r--app/services/merge_requests/delete_non_latest_diffs_service.rb4
-rw-r--r--app/services/merge_requests/merge_service.rb4
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb4
-rw-r--r--app/services/merge_requests/migrate_external_diffs_service.rb23
-rw-r--r--app/services/merge_requests/refresh_service.rb11
-rw-r--r--app/services/milestones/promote_service.rb4
-rw-r--r--app/services/projects/after_import_service.rb2
-rw-r--r--app/services/projects/housekeeping_service.rb5
-rw-r--r--app/services/quick_actions/interpret_service.rb619
-rw-r--r--app/services/releases/destroy_service.rb1
-rw-r--r--app/services/suggestions/apply_service.rb4
-rw-r--r--app/services/suggestions/create_service.rb46
-rw-r--r--app/services/suggestions/outdate_service.rb19
-rw-r--r--app/uploaders/object_storage.rb6
-rw-r--r--app/validators/cluster_name_validator.rb8
-rw-r--r--app/views/admin/runners/_runner.html.haml2
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml2
-rw-r--r--app/views/clusters/clusters/show.html.haml2
-rw-r--r--app/views/clusters/platforms/kubernetes/_form.html.haml14
-rw-r--r--app/views/dashboard/groups/_groups.html.haml4
-rw-r--r--app/views/discussions/_diff_with_notes.html.haml4
-rw-r--r--app/views/explore/groups/_groups.html.haml4
-rw-r--r--app/views/groups/_archived_projects.html.haml4
-rw-r--r--app/views/groups/_shared_projects.html.haml4
-rw-r--r--app/views/groups/_subgroups_and_projects.html.haml4
-rw-r--r--app/views/help/index.html.haml2
-rw-r--r--app/views/help/instance_configuration.html.haml2
-rw-r--r--app/views/help/show.html.haml2
-rw-r--r--app/views/help/ui.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml6
-rw-r--r--app/views/notify/_note_email.text.erb4
-rw-r--r--app/views/profiles/show.html.haml4
-rw-r--r--app/views/projects/_md_preview.html.haml4
-rw-r--r--app/views/projects/_wiki.html.haml5
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/blob/diff.html.haml6
-rw-r--r--app/views/projects/blob/preview.html.haml41
-rw-r--r--app/views/projects/blob/viewers/_markup.html.haml2
-rw-r--r--app/views/projects/diffs/_line.html.haml2
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml8
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/merge_requests/_mr_box.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml2
-rw-r--r--app/views/projects/merge_requests/conflicts/show.html.haml4
-rw-r--r--app/views/projects/milestones/show.html.haml5
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml9
-rw-r--r--app/views/projects/tags/_tag.html.haml5
-rw-r--r--app/views/projects/tags/show.html.haml5
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/search/results/_blob.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml2
-rw-r--r--app/views/search/results/_wiki_blob.html.haml2
-rw-r--r--app/views/shared/milestones/_milestone.html.haml2
-rw-r--r--app/views/shared/milestones/_top.html.haml5
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/workers/all_queues.yml2
-rw-r--r--app/workers/create_gpg_signature_worker.rb4
-rw-r--r--app/workers/migrate_external_diffs_worker.rb12
-rw-r--r--app/workers/post_receive.rb8
-rw-r--r--app/workers/schedule_migrate_external_diffs_worker.rb14
-rw-r--r--changelogs/unreleased/10735-geo-gitlab-revision-can-return-not-consistent-results.yml5
-rw-r--r--changelogs/unreleased/24936-remove-type-from-review-app-name.yml5
-rw-r--r--changelogs/unreleased/48132-display-output-from-pre-receive-scripts.yml5
-rw-r--r--changelogs/unreleased/49856-upgrade-bootstrap-form-gem.yml5
-rw-r--r--changelogs/unreleased/49910-reopening-a-closed-milestone-from-the-closed-milestones-page-fails2.yml5
-rw-r--r--changelogs/unreleased/51988-install-group-runner-on-group-cluster.yml5
-rw-r--r--changelogs/unreleased/52366-improved-group-lists-ui-spinners.yml5
-rw-r--r--changelogs/unreleased/53210-add-uniq-constraints-on-issues-and-mrs-labels.yml5
-rw-r--r--changelogs/unreleased/54670-external-diffs-when-outdated.yml5
-rw-r--r--changelogs/unreleased/54916-extended-tooltip-for-merge-request-links.yml5
-rw-r--r--changelogs/unreleased/57669-fix-bug-clicking-file-header-refreshes-page.yml6
-rw-r--r--changelogs/unreleased/58793-fix-nav-links-archived-project.yml5
-rw-r--r--changelogs/unreleased/58805-allow-incomplete-commit-data-to-be-fetched-from-collection.yml5
-rw-r--r--changelogs/unreleased/58971-sentry-api-keyerror.yml5
-rw-r--r--changelogs/unreleased/59062-update-gitlab-markup-python-3.yml5
-rw-r--r--changelogs/unreleased/59079-fix-jupyter-render-loop.yml5
-rw-r--r--changelogs/unreleased/59273-update-fugit.yml5
-rw-r--r--changelogs/unreleased/59296-add-filter-by-title-milestones-api.yml5
-rw-r--r--changelogs/unreleased/59352-fix-mr-discussion-expansion.yml5
-rw-r--r--changelogs/unreleased/59441-add-base-domain-to-cluster-api.yml5
-rw-r--r--changelogs/unreleased/59502-fix-breadcrumb-artifacts.yml5
-rw-r--r--changelogs/unreleased/59546-fix-error-handling-for-missing-domain.yml5
-rw-r--r--changelogs/unreleased/MaxWinterstein-master-patch-23232.yml5
-rw-r--r--changelogs/unreleased/check-mergeability-in-merge-to-ref-service.yml5
-rw-r--r--changelogs/unreleased/delete-release-when-delete-tag.yml5
-rw-r--r--changelogs/unreleased/fix-container-scanning-on-k8s.yml5
-rw-r--r--changelogs/unreleased/fix-hidden-statistics.yml5
-rw-r--r--changelogs/unreleased/fix-routes-n-plus-one-in-user-autocomplete.yml5
-rw-r--r--changelogs/unreleased/fix-transfer-group-possibilities.yml5
-rw-r--r--changelogs/unreleased/issue_58547.yml5
-rw-r--r--changelogs/unreleased/knative-update.yml5
-rw-r--r--changelogs/unreleased/kube_helm_auto_devops_213.yml5
-rw-r--r--changelogs/unreleased/nfriend-css-updates-for-gitlab-design-system-compliance.yml5
-rw-r--r--changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml5
-rw-r--r--changelogs/unreleased/persist-fulll-ref-path-for-mr-pipelines.yml5
-rw-r--r--changelogs/unreleased/recreate-all-diffs-on-import.yml5
-rw-r--r--changelogs/unreleased/related-mr-link-cutoff.yml5
-rw-r--r--changelogs/unreleased/sh-add-gitaly-duration-logs.yml5
-rw-r--r--changelogs/unreleased/sh-clear-pipeline-status-cache-upon-destroy.yml5
-rw-r--r--changelogs/unreleased/sh-fix-gitaly-find-commit-caching.yml5
-rw-r--r--changelogs/unreleased/sh-fix-project-branches-merge-status.yml5
-rw-r--r--changelogs/unreleased/sh-force-gc-after-import.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-projects-api.yml5
-rw-r--r--config/gitlab.yml.example8
-rw-r--r--config/helpers/is_ee_env.js9
-rw-r--r--config/initializers/1_settings.rb5
-rw-r--r--config/initializers/jira.rb11
-rw-r--r--config/initializers/lograge.rb7
-rw-r--r--config/karma.config.js2
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--config/webpack.config.js6
-rw-r--r--db/fixtures/development/25_api_personal_access_token.rb15
-rw-r--r--db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb42
-rw-r--r--db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb19
-rw-r--r--db/migrate/20190315191339_create_merge_request_assignees_table.rb22
-rw-r--r--db/migrate/20190325165127_add_managed_to_cluster.rb17
-rw-r--r--db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb23
-rw-r--r--db/schema.rb19
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/custom_hooks.md20
-rw-r--r--doc/administration/gitaly/index.md76
-rw-r--r--doc/administration/high_availability/nfs.md5
-rw-r--r--doc/administration/img/custom_hooks_error_msg.pngbin44892 -> 80442 bytes
-rw-r--r--doc/administration/logs.md13
-rw-r--r--doc/administration/merge_request_diffs.md44
-rw-r--r--doc/administration/pages/index.md29
-rw-r--r--doc/administration/repository_storage_paths.md2
-rw-r--r--doc/administration/repository_storage_types.md3
-rw-r--r--doc/api/build_triggers.md6
-rw-r--r--doc/api/builds.md4
-rw-r--r--doc/api/commits.md30
-rw-r--r--doc/api/group_milestones.md2
-rw-r--r--doc/api/milestones.md2
-rw-r--r--doc/api/project_clusters.md7
-rw-r--r--doc/api/services.md2
-rw-r--r--doc/api/suggestions.md2
-rw-r--r--doc/articles/artifactory_and_gitlab/index.md4
-rw-r--r--doc/articles/how_to_configure_ldap_gitlab_ce/index.md4
-rw-r--r--doc/articles/how_to_install_git/index.md4
-rw-r--r--doc/articles/laravel_with_gitlab_and_envoy/index.md4
-rw-r--r--doc/articles/numerous_undo_possibilities_in_git/index.md4
-rw-r--r--doc/articles/runner_autoscale_aws/index.md4
-rw-r--r--doc/ci/README.md45
-rw-r--r--doc/ci/environments.md681
-rw-r--r--doc/ci/environments/protected_environments.md48
-rw-r--r--doc/ci/introduction/img/gitlab_workflow_example.pngbin55394 -> 0 bytes
-rw-r--r--doc/ci/introduction/img/gitlab_workflow_example_11_9.pngbin0 -> 71292 bytes
-rw-r--r--doc/ci/introduction/index.md2
-rw-r--r--doc/ci/pipelines.md4
-rw-r--r--doc/ci/quick_start/README.md2
-rw-r--r--doc/ci/variables/README.md37
-rw-r--r--doc/ci/variables/deprecated_variables.md27
-rw-r--r--doc/ci/yaml/README.md25
-rw-r--r--doc/customization/welcome_message.md4
-rw-r--r--doc/development/contributing/index.md7
-rw-r--r--doc/development/contributing/style_guides.md1
-rw-r--r--doc/development/ee_features.md13
-rw-r--r--doc/development/fe_guide/testing.md6
-rw-r--r--doc/development/gitaly.md79
-rw-r--r--doc/development/go_guide/index.md4
-rw-r--r--doc/development/i18n/proofreader.md3
-rw-r--r--doc/development/i18n_guide.md6
-rw-r--r--doc/development/python_guide/index.md79
-rw-r--r--doc/development/sql.md15
-rw-r--r--doc/development/testing.md6
-rw-r--r--doc/development/testing_guide/best_practices.md15
-rw-r--r--doc/development/testing_guide/end_to_end_tests.md18
-rw-r--r--doc/development/testing_guide/index.md2
-rw-r--r--doc/gitlab-basics/add-merge-request.md2
-rw-r--r--doc/gitlab-basics/create-group.md3
-rw-r--r--doc/gitlab-basics/create-issue.md3
-rw-r--r--doc/gitlab-basics/create-project.md8
-rw-r--r--doc/install/aws/index.md10
-rw-r--r--doc/markdown/markdown.md6
-rw-r--r--doc/operations/README.md4
-rw-r--r--doc/operations/cleaning_up_redis_sessions.md6
-rw-r--r--doc/operations/moving_repositories.md6
-rw-r--r--doc/operations/sidekiq_memory_killer.md6
-rw-r--r--doc/operations/unicorn.md6
-rw-r--r--doc/pages/README.md6
-rw-r--r--doc/pages/administration.md6
-rw-r--r--doc/pages/getting_started_part_one.md4
-rw-r--r--doc/pages/getting_started_part_three.md4
-rw-r--r--doc/pages/getting_started_part_two.md4
-rw-r--r--doc/raketasks/check.md6
-rw-r--r--doc/raketasks/maintenance.md6
-rw-r--r--doc/topics/autodevops/index.md61
-rw-r--r--doc/user/gitlab_com/index.md8
-rw-r--r--doc/user/group/clusters/index.md3
-rw-r--r--doc/user/markdown.md15
-rw-r--r--doc/user/permissions.md2
-rw-r--r--doc/user/profile/account/two_factor_authentication.md7
-rw-r--r--doc/user/profile/personal_access_tokens.md7
-rw-r--r--doc/user/project/clusters/index.md123
-rw-r--r--doc/user/project/index.md4
-rw-r--r--doc/user/project/integrations/webhooks.md30
-rw-r--r--doc/user/project/merge_requests/index.md2
-rw-r--r--doc/user/project/pipelines/schedules.md121
-rw-r--r--doc/user/project/wiki/index.md8
-rw-r--r--doc/workflow/add-user/add-user.md4
-rw-r--r--doc/workflow/authorization_for_merge_requests.md4
-rw-r--r--doc/workflow/award_emoji.md4
-rw-r--r--doc/workflow/cherry_pick_changes.md6
-rw-r--r--doc/workflow/importing/README.md4
-rw-r--r--doc/workflow/importing/import_projects_from_bitbucket.md4
-rw-r--r--doc/workflow/importing/import_projects_from_fogbugz.md4
-rw-r--r--doc/workflow/importing/import_projects_from_gitea.md4
-rw-r--r--doc/workflow/importing/import_projects_from_github.md4
-rw-r--r--doc/workflow/importing/import_projects_from_gitlab_com.md4
-rw-r--r--doc/workflow/importing/migrating_from_svn.md4
-rw-r--r--doc/workflow/labels.md4
-rw-r--r--doc/workflow/lfs/lfs_administration.md12
-rw-r--r--doc/workflow/merge_requests.md4
-rw-r--r--doc/workflow/merge_when_build_succeeds.md4
-rw-r--r--doc/workflow/milestones.md4
-rw-r--r--doc/workflow/project_features.md4
-rw-r--r--doc/workflow/protected_branches.md4
-rw-r--r--doc/workflow/revert_changes.md4
-rw-r--r--doc/workflow/share_projects_with_other_groups.md4
-rw-r--r--doc/workflow/share_with_group.md4
-rw-r--r--doc/workflow/web_editor.md4
-rw-r--r--doc/workflow/wip_merge_requests.md4
-rw-r--r--jest.config.js6
-rw-r--r--lib/api/branches.rb4
-rw-r--r--lib/api/entities.rb6
-rw-r--r--lib/api/groups.rb15
-rw-r--r--lib/api/helpers.rb6
-rw-r--r--lib/api/helpers/internal_helpers.rb16
-rw-r--r--lib/api/helpers/issues_helpers.rb23
-rw-r--r--lib/api/helpers/projects_helpers.rb39
-rw-r--r--lib/api/internal.rb2
-rw-r--r--lib/api/issues.rb27
-rw-r--r--lib/api/milestone_responses.rb2
-rw-r--r--lib/api/project_clusters.rb2
-rw-r--r--lib/api/projects.rb51
-rw-r--r--lib/api/protected_branches.rb24
-rw-r--r--lib/api/settings.rb36
-rw-r--r--lib/api/users.rb8
-rw-r--r--lib/api/variables.rb8
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb11
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb12
-rw-r--r--lib/banzai/suggestions_parser.rb2
-rw-r--r--lib/gitlab.rb2
-rw-r--r--lib/gitlab/background_migration/populate_merge_request_assignees_table.rb36
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb7
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/abilities.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/validate/repository.rb2
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml15
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml15
-rw-r--r--lib/gitlab/current_settings.rb2
-rw-r--r--lib/gitlab/danger/helper.rb18
-rw-r--r--lib/gitlab/diff/file.rb8
-rw-r--r--lib/gitlab/diff/suggestion.rb52
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb41
-rw-r--r--lib/gitlab/encoding_helper.rb7
-rw-r--r--lib/gitlab/git/commit.rb6
-rw-r--r--lib/gitlab/git/pre_receive_error.rb31
-rw-r--r--lib/gitlab/gitaly_client.rb40
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb2
-rw-r--r--lib/gitlab/gitaly_client/conflicts_service.rb2
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb4
-rw-r--r--lib/gitlab/gitaly_client/wiki_service.rb4
-rw-r--r--lib/gitlab/gl_repository.rb35
-rw-r--r--lib/gitlab/gl_repository/repo_type.rb42
-rw-r--r--lib/gitlab/grape_logging/loggers/perf_logger.rb5
-rw-r--r--lib/gitlab/import/merge_request_helpers.rb16
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/quick_actions/command_definition.rb8
-rw-r--r--lib/gitlab/quick_actions/commit_actions.rb31
-rw-r--r--lib/gitlab/quick_actions/common_actions.rb17
-rw-r--r--lib/gitlab/quick_actions/dsl.rb23
-rw-r--r--lib/gitlab/quick_actions/issuable_actions.rb221
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb136
-rw-r--r--lib/gitlab/quick_actions/issue_and_merge_request_actions.rb225
-rw-r--r--lib/gitlab/quick_actions/merge_request_actions.rb59
-rw-r--r--lib/gitlab/repo_path.rb25
-rw-r--r--lib/gitlab/workhorse.rb4
-rw-r--r--lib/sentry/client.rb32
-rw-r--r--lib/tasks/gitlab/db.rake5
-rw-r--r--lib/tasks/tokens.rake2
-rw-r--r--locale/gitlab.pot245
-rw-r--r--package.json31
-rw-r--r--qa/README.md31
-rw-r--r--qa/Rakefile6
-rw-r--r--qa/load/artillery.yml17
-rw-r--r--qa/qa.rb2
-rw-r--r--qa/qa/page/dashboard/groups.rb6
-rw-r--r--qa/qa/page/dashboard/snippet/new.rb2
-rw-r--r--qa/qa/page/group/show.rb2
-rw-r--r--qa/qa/page/label/index.rb2
-rw-r--r--qa/qa/page/label/new.rb2
-rw-r--r--qa/qa/page/main/menu.rb6
-rw-r--r--qa/qa/page/merge_request/show.rb4
-rw-r--r--qa/qa/page/profile/personal_access_tokens.rb2
-rw-r--r--qa/qa/page/project/issue/index.rb2
-rw-r--r--qa/qa/page/project/job/show.rb6
-rw-r--r--qa/qa/page/project/milestone/new.rb2
-rw-r--r--qa/qa/page/project/new.rb6
-rw-r--r--qa/qa/page/project/operations/environments/index.rb2
-rw-r--r--qa/qa/page/project/pipeline/index.rb2
-rw-r--r--qa/qa/page/project/pipeline/show.rb4
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb10
-rw-r--r--qa/qa/page/project/show.rb4
-rw-r--r--qa/qa/page/project/sub_menus/operations.rb4
-rw-r--r--qa/qa/page/project/sub_menus/repository.rb2
-rw-r--r--qa/qa/page/project/sub_menus/settings.rb8
-rw-r--r--qa/qa/page/project/wiki/edit.rb10
-rw-r--r--qa/qa/page/project/wiki/new.rb2
-rw-r--r--qa/qa/page/project/wiki/show.rb2
-rw-r--r--qa/qa/resource/api_fabricator.rb3
-rw-r--r--qa/qa/resource/branch.rb2
-rw-r--r--qa/qa/resource/ci_variable.rb2
-rw-r--r--qa/qa/resource/deploy_key.rb2
-rw-r--r--qa/qa/resource/deploy_token.rb2
-rw-r--r--qa/qa/resource/group.rb2
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb2
-rw-r--r--qa/qa/resource/label.rb4
-rw-r--r--qa/qa/resource/personal_access_token.rb4
-rw-r--r--qa/qa/resource/project_imported_from_github.rb4
-rw-r--r--qa/qa/resource/project_milestone.rb2
-rw-r--r--qa/qa/resource/repository/wiki_push.rb2
-rw-r--r--qa/qa/resource/runner.rb2
-rw-r--r--qa/qa/resource/sandbox.rb4
-rw-r--r--qa/qa/resource/settings/hashed_storage.rb2
-rw-r--r--qa/qa/resource/snippet.rb2
-rw-r--r--qa/qa/resource/ssh_key.rb2
-rw-r--r--qa/qa/resource/wiki.rb2
-rw-r--r--qa/qa/runtime/address.rb7
-rw-r--r--qa/qa/runtime/api/client.rb2
-rw-r--r--qa/qa/runtime/env.rb2
-rw-r--r--qa/qa/runtime/feature.rb36
-rw-r--r--qa/qa/scenario/bootable.rb10
-rw-r--r--qa/qa/scenario/shared_attributes.rb12
-rw-r--r--qa/qa/scenario/template.rb31
-rw-r--r--qa/qa/scenario/test/instance/all.rb1
-rw-r--r--qa/qa/scenario/test/instance/smoke.rb1
-rw-r--r--qa/qa/scenario/test/integration/mattermost.rb9
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb2
-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.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb7
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb17
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb2
-rw-r--r--qa/qa/support/api.rb3
-rw-r--r--qa/qa/tools/delete_subgroups.rb6
-rw-r--r--qa/qa/tools/generate_perf_testdata.rb8
-rw-r--r--qa/qa/tools/revoke_all_personal_access_tokens.rb2
-rw-r--r--qa/spec/runtime/env_spec.rb6
-rw-r--r--qa/spec/runtime/feature_spec.rb41
-rw-r--r--qa/spec/runtime/scenario_spec.rb8
-rw-r--r--qa/spec/scenario/bootable_spec.rb9
-rw-r--r--qa/spec/scenario/template_spec.rb28
-rw-r--r--qa/spec/scenario/test/integration/github_spec.rb2
-rw-r--r--qa/spec/scenario/test/integration/mattermost_spec.rb11
-rw-r--r--qa/spec/shared_examples/scenario_shared_examples.rb40
-rw-r--r--rubocop/cop/migration/update_column_in_batches.rb2
-rw-r--r--scripts/frontend/stylelint/stylelint-duplicate-selectors.js23
-rw-r--r--scripts/frontend/stylelint/stylelint-utility-classes.js24
-rw-r--r--scripts/frontend/stylelint/stylelint-utility-map.js54
-rw-r--r--scripts/frontend/stylelint/stylelint-utils.js77
-rw-r--r--scripts/frontend/stylelint/utility-classes-map.js216
-rwxr-xr-xscripts/insert-rspec-profiling-data2
-rwxr-xr-xscripts/review_apps/review-apps.sh286
-rw-r--r--spec/controllers/graphql_controller_spec.rb45
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb4
-rw-r--r--spec/factories/clusters/clusters.rb5
-rw-r--r--spec/factories/merge_requests.rb33
-rw-r--r--spec/factories/suggestions.rb6
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb80
-rw-r--r--spec/features/issues/user_uses_quick_actions_spec.rb97
-rw-r--r--spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb38
-rw-r--r--spec/features/merge_request/user_uses_quick_actions_spec.rb57
-rw-r--r--spec/features/merge_request/user_views_open_merge_request_spec.rb2
-rw-r--r--spec/features/milestone_spec.rb28
-rw-r--r--spec/features/projects/artifacts/user_browses_artifacts_spec.rb6
-rw-r--r--spec/features/projects/environments/environment_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb38
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb2
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb2
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb2
-rw-r--r--spec/features/tags/master_deletes_tag_spec.rb2
-rw-r--r--spec/features/task_lists_spec.rb8
-rw-r--r--spec/features/users/login_spec.rb2
-rw-r--r--spec/frontend/.eslintrc.yml9
-rw-r--r--spec/frontend/environment.js52
-rw-r--r--spec/frontend/helpers/class_spec_helper.js9
-rw-r--r--spec/frontend/helpers/fixtures.js28
-rw-r--r--spec/frontend/helpers/locale_helper.js11
-rw-r--r--spec/frontend/helpers/scroll_into_view_promise.js28
-rw-r--r--spec/frontend/helpers/set_timeout_promise_helper.js4
-rw-r--r--spec/frontend/helpers/user_mock_data_helper.js14
-rw-r--r--spec/frontend/helpers/vue_component_helper.js18
-rw-r--r--spec/frontend/helpers/vue_resource_helper.js11
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper.js19
-rw-r--r--spec/frontend/helpers/vuex_action_helper.js104
-rw-r--r--spec/frontend/helpers/wait_for_attribute_change.js16
-rw-r--r--spec/frontend/helpers/wait_for_promises.js1
-rw-r--r--spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap93
-rw-r--r--spec/frontend/mr_popover/mr_popover_spec.js61
-rw-r--r--spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js (renamed from spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js)6
-rw-r--r--spec/frontend/reports/components/report_section_spec.js (renamed from spec/javascripts/reports/components/report_section_spec.js)2
-rw-r--r--spec/frontend/test_setup.js42
-rw-r--r--spec/graphql/types/ci/detailed_status_type_spec.rb11
-rw-r--r--spec/graphql/types/permission_types/issue_spec.rb4
-rw-r--r--spec/graphql/types/permission_types/project_spec.rb4
-rw-r--r--spec/helpers/appearances_helper_spec.rb8
-rw-r--r--spec/helpers/auth_helper_spec.rb2
-rw-r--r--spec/helpers/groups_helper_spec.rb35
-rw-r--r--spec/helpers/issuables_helper_spec.rb6
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb2
-rw-r--r--spec/javascripts/activities_spec.js2
-rw-r--r--spec/javascripts/ajax_loading_spinner_spec.js2
-rw-r--r--spec/javascripts/awards_handler_spec.js4
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js4
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js4
-rw-r--r--spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js2
-rw-r--r--spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js4
-rw-r--r--spec/javascripts/blob/blob_file_dropzone_spec.js4
-rw-r--r--spec/javascripts/blob/notebook/index_spec.js4
-rw-r--r--spec/javascripts/blob/pdf/index_spec.js4
-rw-r--r--spec/javascripts/blob/sketch/index_spec.js4
-rw-r--r--spec/javascripts/blob/viewer/index_spec.js4
-rw-r--r--spec/javascripts/boards/components/board_spec.js2
-rw-r--r--spec/javascripts/bootstrap_linked_tabs_spec.js4
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js8
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js16
-rw-r--r--spec/javascripts/ci_variable_list/native_form_variable_list_spec.js4
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js4
-rw-r--r--spec/javascripts/clusters/components/applications_spec.js2
-rw-r--r--spec/javascripts/collapsed_sidebar_todo_spec.js2
-rw-r--r--spec/javascripts/create_item_dropdown_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js74
-rw-r--r--spec/javascripts/diffs/mock_data/diff_discussions.js2
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js2
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js2
-rw-r--r--spec/javascripts/fixtures/.gitignore1
-rw-r--r--spec/javascripts/fixtures/abuse_reports.rb2
-rw-r--r--spec/javascripts/fixtures/admin_users.rb2
-rw-r--r--spec/javascripts/fixtures/application_settings.rb2
-rw-r--r--spec/javascripts/fixtures/blob.rb2
-rw-r--r--spec/javascripts/fixtures/boards.rb2
-rw-r--r--spec/javascripts/fixtures/branches.rb2
-rw-r--r--spec/javascripts/fixtures/clusters.rb2
-rw-r--r--spec/javascripts/fixtures/commit.rb2
-rw-r--r--spec/javascripts/fixtures/groups.rb4
-rw-r--r--spec/javascripts/fixtures/issues.rb10
-rw-r--r--spec/javascripts/fixtures/jobs.rb2
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb10
-rw-r--r--spec/javascripts/fixtures/pipeline_schedules.rb4
-rw-r--r--spec/javascripts/fixtures/projects.rb10
-rw-r--r--spec/javascripts/fixtures/prometheus_service.rb2
-rw-r--r--spec/javascripts/fixtures/search.rb2
-rw-r--r--spec/javascripts/fixtures/services.rb2
-rw-r--r--spec/javascripts/fixtures/sessions.rb2
-rw-r--r--spec/javascripts/fixtures/snippet.rb2
-rw-r--r--spec/javascripts/fixtures/static/ajax_loading_spinner.html (renamed from spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/balsamiq_viewer.html (renamed from spec/javascripts/fixtures/static/balsamiq_viewer.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/create_item_dropdown.html (renamed from spec/javascripts/fixtures/static/create_item_dropdown.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/event_filter.html (renamed from spec/javascripts/fixtures/static/event_filter.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/gl_dropdown.html (renamed from spec/javascripts/fixtures/static/gl_dropdown.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/gl_field_errors.html (renamed from spec/javascripts/fixtures/static/gl_field_errors.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/issuable_filter.html (renamed from spec/javascripts/fixtures/static/issuable_filter.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/issue_sidebar_label.html (renamed from spec/javascripts/fixtures/static/issue_sidebar_label.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/line_highlighter.html (renamed from spec/javascripts/fixtures/static/line_highlighter.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/linked_tabs.html (renamed from spec/javascripts/fixtures/static/linked_tabs.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/merge_requests_show.html (renamed from spec/javascripts/fixtures/static/merge_requests_show.html.raw)2
-rw-r--r--spec/javascripts/fixtures/static/mini_dropdown_graph.html (renamed from spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/notebook_viewer.html (renamed from spec/javascripts/fixtures/static/notebook_viewer.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/oauth_remember_me.html (renamed from spec/javascripts/fixtures/static/oauth_remember_me.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/pdf_viewer.html (renamed from spec/javascripts/fixtures/static/pdf_viewer.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/pipeline_graph.html (renamed from spec/javascripts/fixtures/static/pipeline_graph.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/pipelines.html (renamed from spec/javascripts/fixtures/static/pipelines.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/project_select_combo_button.html (renamed from spec/javascripts/fixtures/static/project_select_combo_button.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/search_autocomplete.html (renamed from spec/javascripts/fixtures/static/search_autocomplete.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/signin_tabs.html (renamed from spec/javascripts/fixtures/static/signin_tabs.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static/sketch_viewer.html (renamed from spec/javascripts/fixtures/static/sketch_viewer.html.raw)0
-rw-r--r--spec/javascripts/fixtures/static_fixtures.rb2
-rw-r--r--spec/javascripts/fixtures/todos.rb2
-rw-r--r--spec/javascripts/fixtures/u2f.rb4
-rw-r--r--spec/javascripts/gl_dropdown_spec.js4
-rw-r--r--spec/javascripts/gl_field_errors_spec.js4
-rw-r--r--spec/javascripts/header_spec.js2
-rw-r--r--spec/javascripts/ide/components/error_message_spec.js2
-rw-r--r--spec/javascripts/ide/stores/actions/file_spec.js74
-rw-r--r--spec/javascripts/integrations/integration_settings_form_spec.js2
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js4
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js4
-rw-r--r--spec/javascripts/issue_spec.js10
-rw-r--r--spec/javascripts/jobs/components/commit_block_spec.js3
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js4
-rw-r--r--spec/javascripts/lazy_loader_spec.js6
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js33
-rw-r--r--spec/javascripts/line_highlighter_spec.js4
-rw-r--r--spec/javascripts/merge_request_spec.js8
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js6
-rw-r--r--spec/javascripts/mini_pipeline_graph_dropdown_spec.js4
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js4
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js48
-rw-r--r--spec/javascripts/monitoring/mock_data.js5
-rw-r--r--spec/javascripts/new_branch_spec.js4
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js12
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js18
-rw-r--r--spec/javascripts/notes_spec.js2
-rw-r--r--spec/javascripts/oauth_remember_me_spec.js4
-rw-r--r--spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js2
-rw-r--r--spec/javascripts/pages/admin/users/new/index_spec.js2
-rw-r--r--spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js4
-rw-r--r--spec/javascripts/pipelines/graph/action_component_spec.js17
-rw-r--r--spec/javascripts/pipelines/graph/stage_column_component_spec.js2
-rw-r--r--spec/javascripts/pipelines/stage_spec.js16
-rw-r--r--spec/javascripts/pipelines_spec.js4
-rw-r--r--spec/javascripts/project_select_combo_button_spec.js2
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js44
-rw-r--r--spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js2
-rw-r--r--spec/javascripts/read_more_spec.js2
-rw-r--r--spec/javascripts/right_sidebar_spec.js2
-rw-r--r--spec/javascripts/search_autocomplete_spec.js4
-rw-r--r--spec/javascripts/search_spec.js2
-rw-r--r--spec/javascripts/settings_panels_spec.js4
-rw-r--r--spec/javascripts/shortcuts_spec.js2
-rw-r--r--spec/javascripts/sidebar/assignees_spec.js13
-rw-r--r--spec/javascripts/sidebar/sidebar_assignees_spec.js4
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js2
-rw-r--r--spec/javascripts/todos_spec.js4
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js4
-rw-r--r--spec/javascripts/u2f/register_spec.js4
-rw-r--r--spec/javascripts/user_popovers_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/markdown/field_spec.js8
-rw-r--r--spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js2
-rw-r--r--spec/javascripts/zen_mode_spec.js2
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb25
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb56
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb18
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb47
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb19
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb15
-rw-r--r--spec/lib/gitlab/diff/suggestion_spec.rb88
-rw-r--r--spec/lib/gitlab/diff/suggestions_parser_spec.rb73
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb13
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb12
-rw-r--r--spec/lib/gitlab/git/pre_receive_error_spec.rb16
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb15
-rw-r--r--spec/lib/gitlab/gitaly_client/operation_service_spec.rb10
-rw-r--r--spec/lib/gitlab/gitaly_client/ref_service_spec.rb10
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb78
-rw-r--r--spec/lib/gitlab/gl_repository/repo_type_spec.rb64
-rw-r--r--spec/lib/gitlab/gl_repository_spec.rb4
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml4
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml11
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb30
-rw-r--r--spec/lib/gitlab/quick_actions/dsl_spec.rb26
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb20
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb10
-rw-r--r--spec/lib/gitlab_spec.rb2
-rw-r--r--spec/lib/sentry/client_spec.rb85
-rw-r--r--spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb26
-rw-r--r--spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb24
-rw-r--r--spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb12
-rw-r--r--spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb20
-rw-r--r--spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb12
-rw-r--r--spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb22
-rw-r--r--spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb47
-rw-r--r--spec/models/ability_spec.rb2
-rw-r--r--spec/models/abuse_report_spec.rb2
-rw-r--r--spec/models/active_session_spec.rb2
-rw-r--r--spec/models/appearance_spec.rb2
-rw-r--r--spec/models/application_setting/term_spec.rb2
-rw-r--r--spec/models/application_setting_spec.rb2
-rw-r--r--spec/models/award_emoji_spec.rb2
-rw-r--r--spec/models/badge_spec.rb2
-rw-r--r--spec/models/badges/group_badge_spec.rb2
-rw-r--r--spec/models/badges/project_badge_spec.rb2
-rw-r--r--spec/models/blob_spec.rb2
-rw-r--r--spec/models/blob_viewer/base_spec.rb2
-rw-r--r--spec/models/blob_viewer/changelog_spec.rb2
-rw-r--r--spec/models/blob_viewer/composer_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/gemspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/gitlab_ci_yml_spec.rb2
-rw-r--r--spec/models/blob_viewer/license_spec.rb2
-rw-r--r--spec/models/blob_viewer/package_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_json_spec.rb2
-rw-r--r--spec/models/blob_viewer/podspec_spec.rb2
-rw-r--r--spec/models/blob_viewer/readme_spec.rb2
-rw-r--r--spec/models/blob_viewer/route_map_spec.rb2
-rw-r--r--spec/models/blob_viewer/server_side_spec.rb2
-rw-r--r--spec/models/board_spec.rb2
-rw-r--r--spec/models/broadcast_message_spec.rb2
-rw-r--r--spec/models/chat_name_spec.rb2
-rw-r--r--spec/models/chat_team_spec.rb2
-rw-r--r--spec/models/ci/artifact_blob_spec.rb2
-rw-r--r--spec/models/ci/bridge_spec.rb2
-rw-r--r--spec/models/ci/build_metadata_spec.rb2
-rw-r--r--spec/models/ci/build_runner_session_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb22
-rw-r--r--spec/models/ci/build_trace_chunks/database_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/fog_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunks/redis_spec.rb2
-rw-r--r--spec/models/ci/build_trace_section_name_spec.rb2
-rw-r--r--spec/models/ci/build_trace_section_spec.rb2
-rw-r--r--spec/models/ci/group_spec.rb2
-rw-r--r--spec/models/ci/group_variable_spec.rb2
-rw-r--r--spec/models/ci/job_artifact_spec.rb2
-rw-r--r--spec/models/ci/legacy_stage_spec.rb2
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb2
-rw-r--r--spec/models/ci/pipeline_schedule_variable_spec.rb2
-rw-r--r--spec/models/ci/pipeline_spec.rb46
-rw-r--r--spec/models/ci/pipeline_variable_spec.rb2
-rw-r--r--spec/models/ci/runner_spec.rb2
-rw-r--r--spec/models/ci/stage_spec.rb2
-rw-r--r--spec/models/ci/trigger_request_spec.rb2
-rw-r--r--spec/models/ci/trigger_spec.rb2
-rw-r--r--spec/models/ci/variable_spec.rb2
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb29
-rw-r--r--spec/models/clusters/applications/helm_spec.rb2
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb2
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/clusters/applications/knative_spec.rb4
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb2
-rw-r--r--spec/models/clusters/applications/runner_spec.rb45
-rw-r--r--spec/models/clusters/cluster_spec.rb16
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb4
-rw-r--r--spec/models/clusters/project_spec.rb2
-rw-r--r--spec/models/clusters/providers/gcp_spec.rb2
-rw-r--r--spec/models/commit_collection_spec.rb88
-rw-r--r--spec/models/commit_range_spec.rb2
-rw-r--r--spec/models/commit_spec.rb2
-rw-r--r--spec/models/commit_status_spec.rb2
-rw-r--r--spec/models/compare_spec.rb2
-rw-r--r--spec/models/concerns/access_requestable_spec.rb2
-rw-r--r--spec/models/concerns/avatarable_spec.rb2
-rw-r--r--spec/models/concerns/awardable_spec.rb2
-rw-r--r--spec/models/concerns/batch_destroy_dependent_associations_spec.rb2
-rw-r--r--spec/models/concerns/blocks_json_serialization_spec.rb2
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb2
-rw-r--r--spec/models/concerns/cacheable_attributes_spec.rb2
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb2
-rw-r--r--spec/models/concerns/chronic_duration_attribute_spec.rb2
-rw-r--r--spec/models/concerns/deployable_spec.rb2
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb2
-rw-r--r--spec/models/concerns/discussion_on_diff_spec.rb2
-rw-r--r--spec/models/concerns/each_batch_spec.rb2
-rw-r--r--spec/models/concerns/editable_spec.rb2
-rw-r--r--spec/models/concerns/expirable_spec.rb2
-rw-r--r--spec/models/concerns/faster_cache_keys_spec.rb2
-rw-r--r--spec/models/concerns/feature_gate_spec.rb2
-rw-r--r--spec/models/concerns/group_descendant_spec.rb2
-rw-r--r--spec/models/concerns/has_status_spec.rb2
-rw-r--r--spec/models/concerns/has_variable_spec.rb2
-rw-r--r--spec/models/concerns/ignorable_column_spec.rb2
-rw-r--r--spec/models/concerns/issuable_spec.rb2
-rw-r--r--spec/models/concerns/loaded_in_group_list_spec.rb2
-rw-r--r--spec/models/concerns/manual_inverse_association_spec.rb2
-rw-r--r--spec/models/concerns/mentionable_spec.rb2
-rw-r--r--spec/models/concerns/milestoneish_spec.rb2
-rw-r--r--spec/models/concerns/noteable_spec.rb2
-rw-r--r--spec/models/concerns/participable_spec.rb2
-rw-r--r--spec/models/concerns/presentable_spec.rb2
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb2
-rw-r--r--spec/models/concerns/prometheus_adapter_spec.rb2
-rw-r--r--spec/models/concerns/protected_ref_access_spec.rb2
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb2
-rw-r--r--spec/models/concerns/redactable_spec.rb2
-rw-r--r--spec/models/concerns/redis_cacheable_spec.rb2
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_discussion_spec.rb2
-rw-r--r--spec/models/concerns/resolvable_note_spec.rb2
-rw-r--r--spec/models/concerns/routable_spec.rb2
-rw-r--r--spec/models/concerns/sha_attribute_spec.rb2
-rw-r--r--spec/models/concerns/sortable_spec.rb2
-rw-r--r--spec/models/concerns/spammable_spec.rb2
-rw-r--r--spec/models/concerns/strip_attribute_spec.rb2
-rw-r--r--spec/models/concerns/subscribable_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_strategies/base_spec.rb2
-rw-r--r--spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb2
-rw-r--r--spec/models/concerns/triggerable_hooks_spec.rb2
-rw-r--r--spec/models/concerns/uniquify_spec.rb2
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/models/conversational_development_index/metric_spec.rb2
-rw-r--r--spec/models/cycle_analytics/code_spec.rb2
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb2
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb2
-rw-r--r--spec/models/cycle_analytics/production_spec.rb2
-rw-r--r--spec/models/cycle_analytics/review_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/cycle_analytics/test_spec.rb2
-rw-r--r--spec/models/cycle_analytics_spec.rb2
-rw-r--r--spec/models/deploy_key_spec.rb2
-rw-r--r--spec/models/deploy_keys_project_spec.rb2
-rw-r--r--spec/models/deploy_token_spec.rb2
-rw-r--r--spec/models/deployment_spec.rb2
-rw-r--r--spec/models/diff_discussion_spec.rb2
-rw-r--r--spec/models/diff_note_spec.rb2
-rw-r--r--spec/models/diff_viewer/base_spec.rb2
-rw-r--r--spec/models/diff_viewer/server_side_spec.rb2
-rw-r--r--spec/models/discussion_spec.rb2
-rw-r--r--spec/models/email_spec.rb2
-rw-r--r--spec/models/environment_spec.rb2
-rw-r--r--spec/models/environment_status_spec.rb2
-rw-r--r--spec/models/error_tracking/project_error_tracking_setting_spec.rb28
-rw-r--r--spec/models/event_collection_spec.rb2
-rw-r--r--spec/models/event_spec.rb2
-rw-r--r--spec/models/external_issue_spec.rb2
-rw-r--r--spec/models/fork_network_member_spec.rb2
-rw-r--r--spec/models/fork_network_spec.rb2
-rw-r--r--spec/models/generic_commit_status_spec.rb2
-rw-r--r--spec/models/global_milestone_spec.rb2
-rw-r--r--spec/models/gpg_key_spec.rb2
-rw-r--r--spec/models/gpg_key_subkey_spec.rb2
-rw-r--r--spec/models/gpg_signature_spec.rb2
-rw-r--r--spec/models/group_custom_attribute_spec.rb2
-rw-r--r--spec/models/group_label_spec.rb2
-rw-r--r--spec/models/group_milestone_spec.rb2
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/models/guest_spec.rb2
-rw-r--r--spec/models/hooks/active_hook_filter_spec.rb2
-rw-r--r--spec/models/hooks/project_hook_spec.rb2
-rw-r--r--spec/models/hooks/service_hook_spec.rb2
-rw-r--r--spec/models/hooks/system_hook_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_log_spec.rb2
-rw-r--r--spec/models/hooks/web_hook_spec.rb2
-rw-r--r--spec/models/identity_spec.rb2
-rw-r--r--spec/models/import_export_upload_spec.rb2
-rw-r--r--spec/models/instance_configuration_spec.rb6
-rw-r--r--spec/models/internal_id_spec.rb2
-rw-r--r--spec/models/issue/metrics_spec.rb2
-rw-r--r--spec/models/issue_collection_spec.rb2
-rw-r--r--spec/models/issue_spec.rb2
-rw-r--r--spec/models/key_spec.rb2
-rw-r--r--spec/models/label_link_spec.rb2
-rw-r--r--spec/models/label_priority_spec.rb2
-rw-r--r--spec/models/label_spec.rb2
-rw-r--r--spec/models/legacy_diff_discussion_spec.rb2
-rw-r--r--spec/models/lfs_download_object_spec.rb2
-rw-r--r--spec/models/lfs_file_lock_spec.rb2
-rw-r--r--spec/models/lfs_object_spec.rb2
-rw-r--r--spec/models/lfs_objects_project_spec.rb2
-rw-r--r--spec/models/license_template_spec.rb15
-rw-r--r--spec/models/list_spec.rb2
-rw-r--r--spec/models/member_spec.rb2
-rw-r--r--spec/models/members/group_member_spec.rb18
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request/metrics_spec.rb2
-rw-r--r--spec/models/merge_request_diff_commit_spec.rb2
-rw-r--r--spec/models/merge_request_diff_file_spec.rb2
-rw-r--r--spec/models/merge_request_diff_spec.rb149
-rw-r--r--spec/models/merge_request_spec.rb137
-rw-r--r--spec/models/milestone_spec.rb2
-rw-r--r--spec/models/namespace_spec.rb2
-rw-r--r--spec/models/network/graph_spec.rb2
-rw-r--r--spec/models/note_diff_file_spec.rb2
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/notification_recipient_spec.rb2
-rw-r--r--spec/models/notification_setting_spec.rb2
-rw-r--r--spec/models/pages_domain_spec.rb2
-rw-r--r--spec/models/personal_access_token_spec.rb2
-rw-r--r--spec/models/programming_language_spec.rb2
-rw-r--r--spec/models/project_authorization_spec.rb2
-rw-r--r--spec/models/project_auto_devops_spec.rb2
-rw-r--r--spec/models/project_custom_attribute_spec.rb2
-rw-r--r--spec/models/project_deploy_token_spec.rb2
-rw-r--r--spec/models/project_feature_spec.rb2
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_import_state_spec.rb2
-rw-r--r--spec/models/project_label_spec.rb2
-rw-r--r--spec/models/project_services/asana_service_spec.rb2
-rw-r--r--spec/models/project_services/assembla_service_spec.rb2
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb2
-rw-r--r--spec/models/project_services/bugzilla_service_spec.rb2
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb2
-rw-r--r--spec/models/project_services/campfire_service_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/issue_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/merge_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/note_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/push_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/wiki_page_message_spec.rb2
-rw-r--r--spec/models/project_services/chat_notification_service_spec.rb2
-rw-r--r--spec/models/project_services/custom_issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb2
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb2
-rw-r--r--spec/models/project_services/external_wiki_service_spec.rb2
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb2
-rw-r--r--spec/models/project_services/gitlab_issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/irker_service_spec.rb2
-rw-r--r--spec/models/project_services/issue_tracker_service_spec.rb2
-rw-r--r--spec/models/project_services/jira_service_spec.rb2
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb2
-rw-r--r--spec/models/project_services/mattermost_service_spec.rb2
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb2
-rw-r--r--spec/models/project_services/packagist_service_spec.rb2
-rw-r--r--spec/models/project_services/pipelines_email_service_spec.rb2
-rw-r--r--spec/models/project_services/pivotaltracker_service_spec.rb2
-rw-r--r--spec/models/project_services/pushover_service_spec.rb2
-rw-r--r--spec/models/project_services/redmine_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_slash_commands_service_spec.rb2
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb2
-rw-r--r--spec/models/project_services/youtrack_service_spec.rb2
-rw-r--r--spec/models/project_snippet_spec.rb2
-rw-r--r--spec/models/project_spec.rb65
-rw-r--r--spec/models/project_statistics_spec.rb2
-rw-r--r--spec/models/project_team_spec.rb2
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/models/protectable_dropdown_spec.rb2
-rw-r--r--spec/models/protected_branch/merge_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch/push_access_level_spec.rb2
-rw-r--r--spec/models/protected_branch_spec.rb2
-rw-r--r--spec/models/protected_tag_spec.rb2
-rw-r--r--spec/models/push_event_payload_spec.rb2
-rw-r--r--spec/models/push_event_spec.rb2
-rw-r--r--spec/models/redirect_route_spec.rb2
-rw-r--r--spec/models/release_spec.rb2
-rw-r--r--spec/models/remote_mirror_spec.rb2
-rw-r--r--spec/models/repository_language_spec.rb2
-rw-r--r--spec/models/repository_spec.rb2
-rw-r--r--spec/models/route_spec.rb2
-rw-r--r--spec/models/sent_notification_spec.rb2
-rw-r--r--spec/models/service_spec.rb2
-rw-r--r--spec/models/snippet_blob_spec.rb2
-rw-r--r--spec/models/snippet_spec.rb2
-rw-r--r--spec/models/spam_log_spec.rb2
-rw-r--r--spec/models/ssh_host_key_spec.rb2
-rw-r--r--spec/models/subscription_spec.rb2
-rw-r--r--spec/models/system_note_metadata_spec.rb2
-rw-r--r--spec/models/term_agreement_spec.rb2
-rw-r--r--spec/models/timelog_spec.rb2
-rw-r--r--spec/models/todo_spec.rb2
-rw-r--r--spec/models/tree_spec.rb2
-rw-r--r--spec/models/trending_project_spec.rb2
-rw-r--r--spec/models/upload_spec.rb2
-rw-r--r--spec/models/user_agent_detail_spec.rb2
-rw-r--r--spec/models/user_callout_spec.rb2
-rw-r--r--spec/models/user_custom_attribute_spec.rb2
-rw-r--r--spec/models/user_interacted_project_spec.rb2
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/models/wiki_directory_spec.rb2
-rw-r--r--spec/models/wiki_page_spec.rb2
-rw-r--r--spec/policies/board_policy_spec.rb8
-rw-r--r--spec/policies/group_policy_spec.rb58
-rw-r--r--spec/policies/namespace_policy_spec.rb2
-rw-r--r--spec/policies/project_policy_spec.rb234
-rw-r--r--spec/policies/project_snippet_policy_spec.rb109
-rw-r--r--spec/presenters/ci/build_runner_presenter_spec.rb38
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb16
-rw-r--r--spec/requests/api/branches_spec.rb26
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb4
-rw-r--r--spec/requests/api/internal_spec.rb6
-rw-r--r--spec/requests/api/project_clusters_spec.rb8
-rw-r--r--spec/requests/api/projects_spec.rb10
-rw-r--r--spec/requests/api/suggestions_spec.rb3
-rw-r--r--spec/rubocop/cop/migration/update_column_in_batches_spec.rb18
-rw-r--r--spec/serializers/merge_request_widget_entity_spec.rb15
-rw-r--r--spec/serializers/suggestion_entity_spec.rb4
-rw-r--r--spec/services/ci/destroy_pipeline_service_spec.rb15
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb2
-rw-r--r--spec/services/error_tracking/list_issues_service_spec.rb24
-rw-r--r--spec/services/git/branch_push_service_spec.rb (renamed from spec/services/git_push_service_spec.rb)2
-rw-r--r--spec/services/git/tag_push_service_spec.rb (renamed from spec/services/git_tag_push_service_spec.rb)2
-rw-r--r--spec/services/issues/create_service_spec.rb14
-rw-r--r--spec/services/issues/update_service_spec.rb10
-rw-r--r--spec/services/merge_requests/create_service_spec.rb48
-rw-r--r--spec/services/merge_requests/ff_merge_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb2
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb20
-rw-r--r--spec/services/merge_requests/migrate_external_diffs_service_spec.rb43
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb79
-rw-r--r--spec/services/projects/after_import_service_spec.rb2
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb13
-rw-r--r--spec/services/projects/participants_service_spec.rb53
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb1
-rw-r--r--spec/services/releases/destroy_service_spec.rb8
-rw-r--r--spec/services/suggestions/apply_service_spec.rb93
-rw-r--r--spec/services/suggestions/create_service_spec.rb73
-rw-r--r--spec/services/suggestions/outdate_service_spec.rb102
-rw-r--r--spec/services/tags/create_service_spec.rb2
-rw-r--r--spec/services/tags/destroy_service_spec.rb16
-rw-r--r--spec/services/verify_pages_domain_service_spec.rb142
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/api/milestones_shared_examples.rb9
-rw-r--r--spec/support/features/issuable_quick_actions_shared_examples.rb389
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb2
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb2
-rw-r--r--spec/support/helpers/license_helper.rb8
-rw-r--r--spec/support/helpers/policy_helpers.rb11
-rw-r--r--spec/support/helpers/stub_worker.rb9
-rw-r--r--spec/support/matchers/issuable_matchers.rb2
-rw-r--r--spec/support/shared_context/policies/project_policy_shared_context.rb88
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb47
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb231
-rw-r--r--spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb110
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb63
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb89
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb88
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb103
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb81
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb83
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb84
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb83
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb95
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb82
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb86
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb82
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb86
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb81
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb84
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb60
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb98
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb85
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb92
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb120
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb102
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb87
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb86
-rw-r--r--spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb25
-rw-r--r--spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb25
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb4
-rw-r--r--spec/uploaders/object_storage_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb52
-rw-r--r--spec/workers/admin_email_worker_spec.rb2
-rw-r--r--spec/workers/archive_trace_worker_spec.rb2
-rw-r--r--spec/workers/authorized_projects_worker_spec.rb2
-rw-r--r--spec/workers/background_migration_worker_spec.rb2
-rw-r--r--spec/workers/build_coverage_worker_spec.rb2
-rw-r--r--spec/workers/build_finished_worker_spec.rb2
-rw-r--r--spec/workers/build_hooks_worker_spec.rb2
-rw-r--r--spec/workers/build_success_worker_spec.rb2
-rw-r--r--spec/workers/build_trace_sections_worker_spec.rb2
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb2
-rw-r--r--spec/workers/ci/build_schedule_worker_spec.rb2
-rw-r--r--spec/workers/cluster_provision_worker_spec.rb2
-rw-r--r--spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb2
-rw-r--r--spec/workers/concerns/application_worker_spec.rb2
-rw-r--r--spec/workers/concerns/cluster_queue_spec.rb2
-rw-r--r--spec/workers/concerns/cronjob_queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/object_importer_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/queue_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_background_queue_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_queue_spec.rb2
-rw-r--r--spec/workers/concerns/project_import_options_spec.rb2
-rw-r--r--spec/workers/concerns/repository_check_queue_spec.rb2
-rw-r--r--spec/workers/concerns/waitable_worker_spec.rb2
-rw-r--r--spec/workers/create_gpg_signature_worker_spec.rb2
-rw-r--r--spec/workers/create_note_diff_file_worker_spec.rb2
-rw-r--r--spec/workers/create_pipeline_worker_spec.rb2
-rw-r--r--spec/workers/delete_diff_files_worker_spec.rb2
-rw-r--r--spec/workers/delete_merged_branches_worker_spec.rb2
-rw-r--r--spec/workers/delete_user_worker_spec.rb2
-rw-r--r--spec/workers/deployments/success_worker_spec.rb2
-rw-r--r--spec/workers/detect_repository_languages_worker_spec.rb2
-rw-r--r--spec/workers/email_receiver_worker_spec.rb2
-rw-r--r--spec/workers/emails_on_push_worker_spec.rb2
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb2
-rw-r--r--spec/workers/expire_build_artifacts_worker_spec.rb2
-rw-r--r--spec/workers/expire_build_instance_artifacts_worker_spec.rb2
-rw-r--r--spec/workers/expire_job_cache_worker_spec.rb2
-rw-r--r--spec/workers/expire_pipeline_cache_worker_spec.rb2
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/advance_stage_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_shell_worker_spec.rb2
-rw-r--r--spec/workers/gitlab_usage_ping_worker_spec.rb2
-rw-r--r--spec/workers/group_destroy_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/migrator_worker_spec.rb2
-rw-r--r--spec/workers/hashed_storage/project_migrate_worker_spec.rb2
-rw-r--r--spec/workers/invalid_gpg_signature_update_worker_spec.rb2
-rw-r--r--spec/workers/issue_due_scheduler_worker_spec.rb2
-rw-r--r--spec/workers/mail_scheduler/issue_due_worker_spec.rb2
-rw-r--r--spec/workers/mail_scheduler/notification_service_worker_spec.rb2
-rw-r--r--spec/workers/merge_worker_spec.rb2
-rw-r--r--spec/workers/migrate_external_diffs_worker_spec.rb25
-rw-r--r--spec/workers/namespaceless_project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/new_issue_worker_spec.rb2
-rw-r--r--spec/workers/new_merge_request_worker_spec.rb2
-rw-r--r--spec/workers/new_note_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_verification_cron_worker_spec.rb2
-rw-r--r--spec/workers/pages_domain_verification_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_hooks_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_metrics_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_notification_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_process_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_schedule_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_success_worker_spec.rb2
-rw-r--r--spec/workers/pipeline_update_worker_spec.rb2
-rw-r--r--spec/workers/plugin_worker_spec.rb2
-rw-r--r--spec/workers/post_receive_spec.rb28
-rw-r--r--spec/workers/process_commit_worker_spec.rb2
-rw-r--r--spec/workers/project_cache_worker_spec.rb2
-rw-r--r--spec/workers/project_destroy_worker_spec.rb2
-rw-r--r--spec/workers/project_export_worker_spec.rb2
-rw-r--r--spec/workers/propagate_service_template_worker_spec.rb2
-rw-r--r--spec/workers/prune_old_events_worker_spec.rb2
-rw-r--r--spec/workers/prune_web_hook_logs_worker_spec.rb2
-rw-r--r--spec/workers/reactive_caching_worker_spec.rb2
-rw-r--r--spec/workers/rebase_worker_spec.rb2
-rw-r--r--spec/workers/remote_mirror_notification_worker_spec.rb2
-rw-r--r--spec/workers/remove_expired_group_links_worker_spec.rb2
-rw-r--r--spec/workers/remove_expired_members_worker_spec.rb2
-rw-r--r--spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/batch_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/clear_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/dispatch_worker_spec.rb2
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb2
-rw-r--r--spec/workers/repository_cleanup_worker_spec.rb2
-rw-r--r--spec/workers/repository_fork_worker_spec.rb2
-rw-r--r--spec/workers/repository_import_worker_spec.rb2
-rw-r--r--spec/workers/repository_remove_remote_worker_spec.rb2
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb2
-rw-r--r--spec/workers/run_pipeline_schedule_worker_spec.rb2
-rw-r--r--spec/workers/schedule_migrate_external_diffs_worker_spec.rb25
-rw-r--r--spec/workers/stage_update_worker_spec.rb2
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb2
-rw-r--r--spec/workers/stuck_import_jobs_worker_spec.rb2
-rw-r--r--spec/workers/stuck_merge_jobs_worker_spec.rb2
-rw-r--r--spec/workers/system_hook_push_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/confidential_issue_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/entity_leave_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/group_private_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/private_features_worker_spec.rb2
-rw-r--r--spec/workers/todos_destroyer/project_private_worker_spec.rb2
-rw-r--r--spec/workers/trending_projects_worker_spec.rb2
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb2
-rw-r--r--spec/workers/update_merge_requests_worker_spec.rb2
-rw-r--r--spec/workers/upload_checksum_worker_spec.rb2
-rw-r--r--spec/workers/wait_for_cluster_creation_worker_spec.rb2
-rw-r--r--yarn.lock517
1372 files changed, 14154 insertions, 4768 deletions
diff --git a/.eslintignore b/.eslintignore
index f78840e67be..9a5e15c86ae 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -9,5 +9,6 @@
/scripts/
/tmp/
/vendor/
+jest.config.js
karma.config.js
webpack.config.js
diff --git a/.eslintrc.yml b/.eslintrc.yml
index b0794bb7434..98a497aa12a 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -9,9 +9,6 @@ plugins:
- import
- html
settings:
- html/html-extensions:
- - '.html'
- - '.html.raw'
import/resolver:
webpack:
config: './config/webpack.config.js'
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 33559812872..ab38c87039e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -249,8 +249,8 @@ package-and-qa:
- ./scripts/trigger-build omnibus
when: manual
only:
- - //@gitlab-org/gitlab-ce
- - //@gitlab-org/gitlab-ee
+ - /.+/@gitlab-org/gitlab-ce
+ - /.+/@gitlab-org/gitlab-ee
# Review docs base
.review-docs: &review-docs
@@ -320,7 +320,7 @@ cloud-native-image:
variables:
GIT_DEPTH: "1"
cache: {}
- when: always
+ when: manual
script:
- gem install gitlab --no-document
- CNG_PROJECT_PATH="gitlab-org/build/CNG" BUILD_TRIGGER_TOKEN=$CI_JOB_TOKEN ./scripts/trigger-build cng
@@ -685,10 +685,10 @@ gitlab:assets:compile:
- public/assets/
<<: *assets-compile-cache
only:
- - //@gitlab-org/gitlab-ce
- - //@gitlab-org/gitlab-ee
- - //@gitlab/gitlabhq
- - //@gitlab/gitlab-ee
+ - /.+/@gitlab-org/gitlab-ce
+ - /.+/@gitlab-org/gitlab-ee
+ - /.+/@gitlab/gitlabhq
+ - /.+/@gitlab/gitlab-ee
tags:
- docker
- gitlab-org
@@ -990,7 +990,7 @@ no_ee_check:
script:
- scripts/no-ee-check
only:
- - //@gitlab-org/gitlab-ce
+ - /.+/@gitlab-org/gitlab-ce
# GitLab Review apps
.review-build-cng-base: &review-build-cng-base
diff --git a/.gitlab/issue_templates/Documentation.md b/.gitlab/issue_templates/Documentation.md
index c0919aeeda4..67602b7b2df 100644
--- a/.gitlab/issue_templates/Documentation.md
+++ b/.gitlab/issue_templates/Documentation.md
@@ -9,7 +9,7 @@
* For information about documentation content and process, see
https://docs.gitlab.com/ee/development/documentation/ -->
-### Type of issue
+<!-- Type of issue -->
<!-- Un-comment the line for the applicable doc issue type to add its label.
Note that all text on that line is deleted upon issue creation. -->
diff --git a/.gitlab/merge_request_templates/Security Release.md b/.gitlab/merge_request_templates/Security Release.md
index 246f2dae009..42314f9b2dd 100644
--- a/.gitlab/merge_request_templates/Security Release.md
+++ b/.gitlab/merge_request_templates/Security Release.md
@@ -7,6 +7,10 @@ See [the general developer security release guidelines](https://gitlab.com/gitla
This merge request _must not_ close the corresponding security issue _unless_ it
targets master.
+When submitting a merge request for CE, a corresponding EE merge request is
+always required. This makes it easier to merge security merge requests, as
+manually merging CE into EE is no longer required.
+
-->
## Related issues
@@ -20,8 +24,8 @@ targets master.
- [ ] Title of this MR is the same as for all backports
- [ ] A [CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html) is added without a `merge_request` value, with `type` set to `security`
- [ ] Add a link to this MR in the `links` section of related issue
-- [ ] Add a link to an EE MR if required
-- [ ] Assign to a reviewer
+- [ ] Set up an EE MR (always required for CE merge requests): EE_MR_LINK_HERE
+- [ ] Assign to a reviewer (that is not a release manager)
## Reviewer checklist
diff --git a/.rubocop.yml b/.rubocop.yml
index 9143966b864..648d59e8062 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -146,6 +146,20 @@ Naming/FileName:
- XSS
- GRPC
+Rails/ApplicationRecord:
+ Enabled: true
+ Exclude:
+ # Models in database migrations should not subclass from ApplicationRecord
+ # as they need to be as decoupled from application code as possible
+ - db/**/*.rb
+ - lib/gitlab/background_migration/**/*.rb
+ - lib/gitlab/database/**/*.rb
+ - spec/**/*.rb
+ - ee/db/**/*.rb
+ - ee/lib/gitlab/background_migration/**/*.rb
+ - ee/lib/ee/gitlab/background_migration/**/*.rb
+ - ee/spec/**/*.rb
+
# GitLab ###################################################################
Gitlab/ModuleWithInstanceVariables:
diff --git a/.scss-lint.yml b/.scss-lint.yml
index 3df66033fa8..2a1fa27fdb5 100644
--- a/.scss-lint.yml
+++ b/.scss-lint.yml
@@ -1,7 +1,9 @@
# Linter Documentation:
# https://github.com/brigade/scss-lint/blob/master/lib/scss_lint/linter/README.md
-scss_files: 'app/assets/stylesheets/**/*.scss'
+scss_files:
+ - 'app/assets/stylesheets/**/*.scss'
+ - 'ee/app/assets/stylesheets/**/*.scss'
exclude:
- 'app/assets/stylesheets/pages/emojis.scss'
diff --git a/.stylelintrc b/.stylelintrc
index c0f21aed292..241d2c94a88 100644
--- a/.stylelintrc
+++ b/.stylelintrc
@@ -1,6 +1,8 @@
{
"plugins":[
- "stylelint-scss"
+ "./scripts/frontend/stylelint/stylelint-duplicate-selectors.js",
+ "./scripts/frontend/stylelint/stylelint-utility-classes.js",
+ "stylelint-scss",
],
"rules":{
"at-rule-blacklist":[
@@ -95,13 +97,15 @@
},
],
"selector-list-comma-newline-after":"always",
- "selector-max-compound-selectors":[5, { "severity": "warning" }],
+ "selector-max-compound-selectors":[3, { "severity": "warning" }],
"selector-max-id":1,
"selector-no-vendor-prefix":true,
"selector-pseudo-element-colon-notation":"double",
"selector-pseudo-element-no-unknown":true,
"shorthand-property-no-redundant-values":true,
"string-quotes":"single",
- "value-no-vendor-prefix":[true, { ignoreValues: ["sticky"] }]
+ "value-no-vendor-prefix":[true, { ignoreValues: ["sticky"] }],
+ "stylelint-gitlab/duplicate-selectors":[true,{ "severity": "warning" }],
+ "stylelint-gitlab/utility-classes":[true,{ "severity": "warning" }],
}
}
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 5e57fb89558..034552a83ee 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.29.0
+1.30.0
diff --git a/Gemfile b/Gemfile
index d44a99b2913..c0815c4a2a6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -116,7 +116,7 @@ gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.8'
gem 'deckar01-task_list', '2.2.0'
-gem 'gitlab-markup', '~> 1.6.5'
+gem 'gitlab-markup', '~> 1.7.0'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'commonmarker', '~> 0.17'
gem 'RedCloth', '~> 4.3.2'
@@ -128,7 +128,7 @@ gem 'asciidoctor', '~> 1.5.8'
gem 'asciidoctor-plantuml', '0.0.8'
gem 'rouge', '~> 3.1'
gem 'truncato', '~> 0.7.11'
-gem 'bootstrap_form', '~> 2.7.0'
+gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.1'
gem 'escape_utils', '~> 1.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 66e12efa4b3..4ebcc6c81b2 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -87,7 +87,9 @@ GEM
debug_inspector (>= 0.0.1)
bootsnap (1.4.1)
msgpack (~> 1.0)
- bootstrap_form (2.7.0)
+ bootstrap_form (4.2.0)
+ actionpack (>= 5.0)
+ activemodel (>= 5.0)
brakeman (4.2.1)
browser (2.5.3)
builder (3.2.3)
@@ -260,7 +262,7 @@ GEM
foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5)
- fugit (1.1.7)
+ fugit (1.1.9)
et-orbi (~> 1.1, >= 1.1.7)
raabro (~> 1.1)
fuubar (2.2.0)
@@ -284,7 +286,7 @@ GEM
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
activerecord (>= 3.2.0, < 6.0)
- gitlab-markup (1.6.5)
+ gitlab-markup (1.7.0)
gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5)
gitlab-styles (2.5.1)
@@ -784,7 +786,7 @@ GEM
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.2)
- rugged (0.28.0)
+ rugged (0.28.1)
safe_yaml (1.0.4)
sanitize (4.6.6)
crass (~> 1.0.2)
@@ -964,7 +966,7 @@ DEPENDENCIES
better_errors (~> 2.5.0)
binding_of_caller (~> 0.8.0)
bootsnap (~> 1.4)
- bootstrap_form (~> 2.7.0)
+ bootstrap_form (~> 4.2.0)
brakeman (~> 4.2)
browser (~> 2.5)
bullet (~> 5.5.0)
@@ -1018,7 +1020,7 @@ DEPENDENCIES
gitaly-proto (~> 1.13.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
- gitlab-markup (~> 1.6.5)
+ gitlab-markup (~> 1.7.0)
gitlab-sidekiq-fetcher (~> 0.4.0)
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.1.1)
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 00000000000..2afbe5fc3f2
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,12 @@
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple"
+verify_ssl = true
+
+[dev-packages]
+
+[packages]
+docutils = "==0.13.1"
+
+[requires]
+python_version = "3.4"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 00000000000..dc23ca89ed2
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,30 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "ec82d5e7c10fd591aeebbc9b7b62d730f7fd70dc52e4e4818834891aa4194c73"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.4"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "docutils": {
+ "hashes": [
+ "sha256:718c0f5fb677be0f34b781e04241c4067cbd9327b66bdd8e763201130f5175be",
+ "sha256:cb3ebcb09242804f84bdbf0b26504077a054da6772c6f4d625f335cc53ebf94d",
+ "sha256:de454f1015958450b72641165c08afe7023cd7e3944396448f2fb1b0ccba9d77"
+ ],
+ "index": "pypi",
+ "version": "==0.13.1"
+ }
+ },
+ "develop": {}
+}
diff --git a/app/assets/images/select2-spinner.gif b/app/assets/images/select2-spinner.gif
new file mode 100644
index 00000000000..5b33f7e54f4
--- /dev/null
+++ b/app/assets/images/select2-spinner.gif
Binary files differ
diff --git a/app/assets/images/select2.png b/app/assets/images/select2.png
new file mode 100644
index 00000000000..1d804ffb996
--- /dev/null
+++ b/app/assets/images/select2.png
Binary files differ
diff --git a/app/assets/images/select2x2.png b/app/assets/images/select2x2.png
new file mode 100644
index 00000000000..4bdd5c961d4
--- /dev/null
+++ b/app/assets/images/select2x2.png
Binary files differ
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
index 58cf057b2c2..318b7f77c7b 100644
--- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
@@ -10,10 +10,10 @@ export class CopyAsGFM {
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
if (isIOS) return;
- $(document).on('copy', '.md, .wiki', e => {
+ $(document).on('copy', '.md', e => {
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection);
});
- $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', e => {
+ $(document).on('copy', 'pre.code.highlight, table.code td.line_content', e => {
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection);
});
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
@@ -99,7 +99,7 @@ export class CopyAsGFM {
}
static transformGFMSelection(documentFragment) {
- const gfmElements = documentFragment.querySelectorAll('.md, .wiki');
+ const gfmElements = documentFragment.querySelectorAll('.md');
switch (gfmElements.length) {
case 0: {
return documentFragment;
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index fc9286d15e6..bfb073fdcdc 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -4,6 +4,7 @@ import renderMath from './render_math';
import renderMermaid from './render_mermaid';
import highlightCurrentUser from './highlight_current_user';
import initUserPopovers from '../../user_popovers';
+import initMRPopovers from '../../mr_popover';
// Render GitLab flavoured Markdown
//
@@ -15,6 +16,7 @@ $.fn.renderGFM = function renderGFM() {
renderMermaid(this.find('.js-render-mermaid'));
highlightCurrentUser(this.find('.gfm-project_member').get());
initUserPopovers(this.find('.gfm-project_member').get());
+ initMRPopovers(this.find('.gfm-merge_request').get());
return this;
};
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index 680f2031409..670f66b005e 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -37,7 +37,7 @@ export default class ShortcutsIssuable extends Shortcuts {
}
// Sanity check: Make sure the selected text comes from a discussion : it can either contain a message...
- let foundMessage = !!documentFragment.querySelector('.md, .wiki');
+ let foundMessage = !!documentFragment.querySelector('.md');
// ... Or come from a message
if (!foundMessage) {
@@ -46,7 +46,7 @@ export default class ShortcutsIssuable extends Shortcuts {
let node = e;
do {
// Text nodes don't define the `matches` method
- if (node.matches && node.matches('.md, .wiki')) {
+ if (node.matches && node.matches('.md')) {
foundMessage = true;
}
node = node.parentNode;
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 5b206b82fe0..d54f9ce552c 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -393,7 +393,6 @@ export default {
<div slot="description" v-html="prometheusDescription"></div>
</application-row>
<application-row
- v-if="isProjectCluster"
id="runner"
:logo-url="gitlabLogo"
:title="applications.runner.title"
@@ -409,9 +408,9 @@ export default {
>
<div slot="description">
{{
- s__(`ClusterIntegration|GitLab Runner connects to this
- project's repository and executes CI/CD jobs,
- pushing results back and deploying,
+ s__(`ClusterIntegration|GitLab Runner connects to the
+ repository and executes CI/CD jobs,
+ pushing results back and deploying
applications to production.`)
}}
</div>
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
index fba30aea9ae..e5e1cbb1e62 100644
--- a/app/assets/javascripts/commons/bootstrap.js
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -16,3 +16,63 @@ $.fn.extend({
.removeClass('disabled');
},
});
+
+/*
+ Starting with bootstrap 4.3.1, bootstrap sanitizes html used for tooltips / popovers.
+ This extends the default whitelists with more elements / attributes:
+ https://getbootstrap.com/docs/4.3/getting-started/javascript/#sanitizer
+ */
+const whitelist = $.fn.tooltip.Constructor.Default.whiteList;
+
+const inputAttributes = ['value', 'type'];
+
+const dataAttributes = [
+ 'data-toggle',
+ 'data-placement',
+ 'data-container',
+ 'data-title',
+ 'data-class',
+ 'data-clipboard-text',
+ 'data-placement',
+];
+
+// Whitelisting data attributes
+whitelist['*'] = [
+ ...whitelist['*'],
+ ...dataAttributes,
+ 'title',
+ 'width height',
+ 'abbr',
+ 'datetime',
+ 'name',
+ 'width',
+ 'height',
+];
+
+// Whitelist missing elements:
+whitelist.label = ['for'];
+whitelist.button = [...inputAttributes];
+whitelist.input = [...inputAttributes];
+
+whitelist.tt = [];
+whitelist.samp = [];
+whitelist.kbd = [];
+whitelist.var = [];
+whitelist.dfn = [];
+whitelist.cite = [];
+whitelist.big = [];
+whitelist.address = [];
+whitelist.dl = [];
+whitelist.dt = [];
+whitelist.dd = [];
+whitelist.abbr = [];
+whitelist.acronym = [];
+whitelist.blockquote = [];
+whitelist.del = [];
+whitelist.ins = [];
+whitelist['gl-emoji'] = [];
+
+// Whitelisting SVG tags and attributes
+whitelist.svg = ['viewBox'];
+whitelist.use = ['xlink:href'];
+whitelist.path = ['d'];
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 0e779e1be9a..58a9605c181 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -170,21 +170,23 @@ export default {
</div>
<gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" />
<template v-else>
- <div v-if="errorMessage" class="diff-viewer">
- <div class="nothing-here-block" v-html="errorMessage"></div>
+ <div :id="`diff-content-${file.file_hash}`">
+ <div v-if="errorMessage" class="diff-viewer">
+ <div class="nothing-here-block" v-html="errorMessage"></div>
+ </div>
+ <div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed">
+ {{ __('This diff is collapsed.') }}
+ <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
+ __('Click to expand it.')
+ }}</a>
+ </div>
+ <diff-content
+ v-else
+ :class="{ hidden: isCollapsed || isFileTooLarge }"
+ :diff-file="file"
+ :help-page-path="helpPagePath"
+ />
</div>
- <div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed">
- {{ __('This diff is collapsed.') }}
- <a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
- __('Click to expand it.')
- }}</a>
- </div>
- <diff-content
- v-else
- :class="{ hidden: isCollapsed || isFileTooLarge }"
- :diff-file="file"
- :help-page-path="helpPagePath"
- />
</template>
<div v-if="isFileTooLarge" class="nothing-here-block diff-collapsed js-too-large-diff">
{{ __('This source diff could not be displayed because it is too large.') }}
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index a5125c3d077..fda7b7c5fd9 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -11,6 +11,7 @@ import { __, s__, sprintf } from '~/locale';
import { diffViewerModes } from '~/ide/constants';
import EditButton from './edit_button.vue';
import DiffStats from './diff_stats.vue';
+import { scrollToElement } from '~/lib/utils/common_utils';
export default {
components: {
@@ -66,6 +67,9 @@ export default {
hasExpandedDiscussions() {
return this.diffHasExpandedDiscussions(this.diffFile);
},
+ diffContentIDSelector() {
+ return `#diff-content-${this.diffFile.file_hash}`;
+ },
icon() {
if (this.diffFile.submodule) {
return 'archive';
@@ -77,6 +81,11 @@ export default {
if (this.diffFile.submodule) {
return this.diffFile.submodule_tree_url || this.diffFile.submodule_link;
}
+
+ if (!this.discussionPath) {
+ return this.diffContentIDSelector;
+ }
+
return this.discussionPath;
},
filePath() {
@@ -128,9 +137,6 @@ export default {
isModeChanged() {
return this.diffFile.viewer.name === diffViewerModes.mode_changed;
},
- showExpandDiffToFullFileEnabled() {
- return gon.features.expandDiffFullFile && !this.diffFile.is_fully_expanded;
- },
},
mounted() {
polyfillSticky(this.$refs.header);
@@ -152,6 +158,18 @@ export default {
handleToggleDiscussions() {
this.toggleFileDiscussions(this.diffFile);
},
+ handleFileNameClick(e) {
+ const isLinkToOtherPage =
+ this.diffFile.submodule_tree_url || this.diffFile.submodule_link || this.discussionPath;
+
+ if (!isLinkToOtherPage) {
+ e.preventDefault();
+ const selector = this.diffContentIDSelector;
+
+ scrollToElement(document.querySelector(selector));
+ window.location.hash = selector;
+ }
+ },
},
};
</script>
@@ -171,7 +189,14 @@ export default {
class="diff-toggle-caret append-right-5"
@click.stop="handleToggle"
/>
- <a v-once ref="titleWrapper" :href="titleLink" class="append-right-4 js-title-wrapper">
+ <a
+ v-once
+ id="diffFile.file_path"
+ ref="titleWrapper"
+ class="append-right-4 js-title-wrapper"
+ :href="titleLink"
+ @click="handleFileNameClick"
+ >
<file-icon
:file-name="filePath"
:size="18"
@@ -258,7 +283,7 @@ export default {
<icon name="external-link" />
</gl-button>
<gl-button
- v-if="showExpandDiffToFullFileEnabled"
+ v-if="!diffFile.is_fully_expanded"
class="expand-file js-expand-file"
@click="toggleFullDiff(diffFile.file_path)"
>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index 93e754fa896..41a80d99850 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -38,36 +38,34 @@ export default {
</script>
<template>
- <div
+ <table
:class="$options.userColorScheme"
:data-commit-id="commitId"
class="code diff-wrap-lines js-syntax-highlight text-file"
>
- <table>
- <tbody>
- <template v-for="(line, index) in diffLines">
- <parallel-diff-table-row
- :key="line.line_code"
- :file-hash="diffFile.file_hash"
- :context-lines-path="diffFile.context_lines_path"
- :line="line"
- :is-bottom="index + 1 === diffLinesLength"
- />
- <parallel-diff-comment-row
- :key="`dcr-${line.line_code || index}`"
- :line="line"
- :diff-file-hash="diffFile.file_hash"
- :line-index="index"
- :help-page-path="helpPagePath"
- />
- <parallel-draft-comment-row
- v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
- :key="`drafts-${index}`"
- :line="line"
- :diff-file-content-sha="diffFile.file_hash"
- />
- </template>
- </tbody>
- </table>
- </div>
+ <tbody>
+ <template v-for="(line, index) in diffLines">
+ <parallel-diff-table-row
+ :key="line.line_code"
+ :file-hash="diffFile.file_hash"
+ :context-lines-path="diffFile.context_lines_path"
+ :line="line"
+ :is-bottom="index + 1 === diffLinesLength"
+ />
+ <parallel-diff-comment-row
+ :key="`dcr-${line.line_code || index}`"
+ :line="line"
+ :diff-file-hash="diffFile.file_hash"
+ :line-index="index"
+ :help-page-path="helpPagePath"
+ />
+ <parallel-draft-comment-row
+ v-if="shouldRenderParallelDraftRow(diffFile.file_hash, line)"
+ :key="`drafts-${index}`"
+ :line="line"
+ :diff-file-content-sha="diffFile.file_hash"
+ />
+ </template>
+ </tbody>
+ </table>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index a092bdfbc6c..c541ea3445b 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -459,12 +459,7 @@ export default {
class="gl-responsive-table-row"
role="row"
>
- <div
- v-gl-tooltip
- :title="model.name"
- class="table-section section-wrap section-15 text-truncate"
- role="gridcell"
- >
+ <div class="table-section section-wrap section-15 text-truncate" role="gridcell">
<div v-if="!model.isFolder" class="table-mobile-header" role="rowheader">
{{ s__('Environments|Environment') }}
</div>
@@ -473,14 +468,28 @@ export default {
<icon :name="deployIconName" />
</span>
- <span v-if="!model.isFolder" class="environment-name table-mobile-content">
- <a class="qa-environment-link" :href="environmentPath"> {{ model.name }} </a>
+ <span
+ v-if="!model.isFolder"
+ v-gl-tooltip
+ :title="model.name"
+ class="environment-name table-mobile-content"
+ >
+ <a class="qa-environment-link" :href="environmentPath">
+ <span v-if="model.size === 1">{{ model.name }}</span>
+ <span v-else>{{ model.name_without_type }}</span>
+ </a>
<span v-if="isProtected" class="badge badge-success">
{{ s__('Environments|protected') }}
</span>
</span>
-
- <span v-else class="folder-name" role="button" @click="onClickFolder">
+ <span
+ v-else
+ v-gl-tooltip
+ :title="model.folderName"
+ class="folder-name"
+ role="button"
+ @click="onClickFolder"
+ >
<icon :name="folderIconName" class="folder-icon" />
<icon name="folder" class="folder-icon" />
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index ff4e16178e8..55613d815ce 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -99,7 +99,7 @@ export default {
/>
<div
- v-if="shouldRenderDeployBoard"
+ v-if="shouldRenderDeployBoard(model)"
:key="`deploy-board-row-${i}`"
class="js-deploy-board-row"
>
diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js
index d42e4f145dc..1e754a4f54f 100644
--- a/app/assets/javascripts/error_tracking/store/actions.js
+++ b/app/assets/javascripts/error_tracking/store/actions.js
@@ -20,7 +20,7 @@ export function startPolling({ commit, dispatch }, endpoint) {
commit(types.SET_LOADING, false);
dispatch('stopPolling');
},
- errorCallback: response => {
+ errorCallback: ({ response }) => {
let errorMessage = '';
if (response && response.data && response.data.message) {
errorMessage = response.data.message;
diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue
index 29dc2d6a8a3..aa50fd8ff62 100644
--- a/app/assets/javascripts/groups/components/app.vue
+++ b/app/assets/javascripts/groups/components/app.vue
@@ -244,7 +244,7 @@ export default {
<gl-loading-icon
v-if="isLoading"
:label="s__('GroupsTree|Loading groups')"
- :size="2"
+ size="md"
class="loading-animation prepend-top-20"
/>
<groups-component
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 58f14bac8c8..732184dc782 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -140,7 +140,7 @@ export default {
'issue-realtime-pre-pulse': preAnimation,
'issue-realtime-trigger-pulse': pulseAnimation,
}"
- class="wiki"
+ class="md"
v-html="descriptionHtml"
></div>
<textarea
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index 7076a79dd5d..b651a6e4bfb 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -39,7 +39,7 @@ export default {
</gl-link>
<clipboard-button
- :text="commit.short_id"
+ :text="commit.id"
:title="__('Copy commit SHA to clipboard')"
css-class="btn btn-clipboard btn-transparent"
/>
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 1af6b63efc9..59930f8d4a3 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -7,6 +7,7 @@ import axios from './axios_utils';
import { getLocationHash } from './url_utility';
import { convertToCamelCase } from './text_utility';
import { isObject } from './type_utility';
+import BreakpointInstance from '../../breakpoints';
export const getPagePath = (index = 0) => {
const page = $('body').attr('data-page') || '';
@@ -193,16 +194,24 @@ export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2;
export const contentTop = () => {
- const perfBar = $('#js-peek').height() || 0;
- const mrTabsHeight = $('.merge-request-tabs').height() || 0;
- const headerHeight = $('.navbar-gitlab').height() || 0;
- const diffFilesChanged = $('.js-diff-files-changed').height() || 0;
- const diffFileLargeEnoughScreen =
- 'matchMedia' in window ? window.matchMedia('min-width: 768') : true;
+ const perfBar = $('#js-peek').outerHeight() || 0;
+ const mrTabsHeight = $('.merge-request-tabs').outerHeight() || 0;
+ const headerHeight = $('.navbar-gitlab').outerHeight() || 0;
+ const diffFilesChanged = $('.js-diff-files-changed').outerHeight() || 0;
+ const mdScreenOrBigger = ['lg', 'md'].includes(BreakpointInstance.getBreakpointSize());
const diffFileTitleBar =
- (diffFileLargeEnoughScreen && $('.diff-file .file-title-flex-parent:visible').height()) || 0;
+ (mdScreenOrBigger && $('.diff-file .file-title-flex-parent:visible').outerHeight()) || 0;
+ const compareVersionsHeaderHeight =
+ (mdScreenOrBigger && $('.mr-version-controls').outerHeight()) || 0;
- return perfBar + mrTabsHeight + headerHeight + diffFilesChanged + diffFileTitleBar;
+ return (
+ perfBar +
+ mrTabsHeight +
+ headerHeight +
+ diffFilesChanged +
+ diffFileTitleBar +
+ compareVersionsHeaderHeight
+ );
};
export const scrollToElement = element => {
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
index c2de0379d23..3cb406b819d 100644
--- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
@@ -16,7 +16,7 @@ import utilsMixin from '../mixins/line_conflict_utils';
},
},
template: `
- <table>
+ <table class="diff-wrap-lines code js-syntax-highlight">
<tr class="line_holder parallel" v-for="section in file.parallelLines">
<template v-for="line in section">
<td class="diff-line-num header" :class="lineCssClass(line)" v-if="line.isHeader"></td>
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 41783d311ef..d453dc1fdb7 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -69,17 +69,17 @@ export default {
appearance && appearance.line && appearance.line.type
? appearance.line.type
: lineTypes.default;
- const lineColor = lineType === lineTypes.threshold ? this.primaryColor : undefined;
+ const lineWidth =
+ appearance && appearance.line && appearance.line.width
+ ? appearance.line.width
+ : undefined;
return {
name: this.formatLegendLabel(query),
data: this.concatenateResults(query.result),
lineStyle: {
type: lineType,
- color: lineColor,
- },
- itemStyle: {
- color: lineColor,
+ width: lineWidth,
},
areaStyle: {
opacity:
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 895a57785bc..ba6a17827f7 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -1,6 +1,8 @@
<script>
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
+import '~/vue_shared/mixins/is_ee';
import Flash from '../../flash';
import MonitoringService from '../services/monitoring_service';
import MonitorAreaChart from './charts/area.vue';
@@ -17,7 +19,10 @@ export default {
GraphGroup,
EmptyState,
Icon,
+ GlDropdown,
+ GlDropdownItem,
},
+
props: {
hasMetrics: {
type: Boolean,
@@ -104,9 +109,29 @@ export default {
}
},
mounted() {
+ this.servicePromises = [
+ this.service
+ .getGraphsData()
+ .then(data => this.store.storeMetrics(data))
+ .catch(() => Flash(s__('Metrics|There was an error while retrieving metrics'))),
+ this.service
+ .getDeploymentData()
+ .then(data => this.store.storeDeploymentData(data))
+ .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
+ ];
if (!this.hasMetrics) {
this.state = 'gettingStarted';
} else {
+ if (this.environmentsEndpoint) {
+ this.servicePromises.push(
+ this.service
+ .getEnvironmentsData()
+ .then(data => this.store.storeEnvironmentsData(data))
+ .catch(() =>
+ Flash(s__('Metrics|There was an error getting environments information.')),
+ ),
+ );
+ }
this.getGraphsData();
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
@@ -122,17 +147,7 @@ export default {
},
getGraphsData() {
this.state = 'loading';
- Promise.all([
- this.service.getGraphsData().then(data => this.store.storeMetrics(data)),
- this.service
- .getDeploymentData()
- .then(data => this.store.storeDeploymentData(data))
- .catch(() => Flash(s__('Metrics|There was an error getting deployment information.'))),
- this.service
- .getEnvironmentsData()
- .then(data => this.store.storeEnvironmentsData(data))
- .catch(() => Flash(s__('Metrics|There was an error getting environments information.'))),
- ])
+ Promise.all(this.servicePromises)
.then(() => {
if (this.store.groups.length < 1) {
this.state = 'noData';
@@ -156,29 +171,22 @@ export default {
<template>
<div v-if="!showEmptyState" class="prometheus-graphs prepend-top-default">
- <div class="environments d-flex align-items-center">
- {{ s__('Metrics|Environment') }}
- <div class="dropdown prepend-left-10">
- <button class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
- <span>{{ currentEnvironmentName }}</span>
- <icon name="chevron-down" />
- </button>
- <div
- v-if="store.environmentsData.length > 0"
- class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"
+ <div v-if="environmentsEndpoint" class="environments d-flex align-items-center">
+ <strong>{{ s__('Metrics|Environment') }}</strong>
+ <gl-dropdown
+ class="prepend-left-10 js-environments-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="currentEnvironmentName"
+ :disabled="store.environmentsData.length === 0"
+ >
+ <gl-dropdown-item
+ v-for="environment in store.environmentsData"
+ :key="environment.id"
+ :active="environment.name === currentEnvironmentName"
+ active-class="is-active"
+ >{{ environment.name }}</gl-dropdown-item
>
- <ul>
- <li v-for="environment in store.environmentsData" :key="environment.id">
- <a
- :href="environment.metrics_path"
- :class="{ 'is-active': environment.name == currentEnvironmentName }"
- class="dropdown-item"
- >{{ environment.name }}</a
- >
- </li>
- </ul>
- </div>
- </div>
+ </gl-dropdown>
</div>
<graph-group
v-for="(groupData, index) in store.groups"
@@ -194,7 +202,17 @@ export default {
:alert-data="getGraphAlerts(graphData.id)"
:container-width="elWidth"
group-id="monitor-area-chart"
- />
+ >
+ <alert-widget
+ v-if="isEE && prometheusAlertsAvailable && alertsEndpoint && graphData.id"
+ :alerts-endpoint="alertsEndpoint"
+ :label="getGraphLabel(graphData)"
+ :current-alerts="getQueryAlerts(graphData)"
+ :custom-metric-id="graphData.id"
+ :alert-data="alertData[graphData.id]"
+ @setAlerts="setAlerts"
+ />
+ </monitor-area-chart>
</graph-group>
</div>
<empty-state
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index 869173b6572..55ecf3b5334 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -6,5 +6,4 @@ export const graphTypes = {
export const lineTypes = {
default: 'solid',
- threshold: 'dashed',
};
diff --git a/app/assets/javascripts/mr_popover/components/mr_popover.vue b/app/assets/javascripts/mr_popover/components/mr_popover.vue
new file mode 100644
index 00000000000..8e2d8fa816a
--- /dev/null
+++ b/app/assets/javascripts/mr_popover/components/mr_popover.vue
@@ -0,0 +1,110 @@
+<script>
+import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
+import Icon from '../../vue_shared/components/icon.vue';
+import CiIcon from '../../vue_shared/components/ci_icon.vue';
+import timeagoMixin from '../../vue_shared/mixins/timeago';
+import query from '../queries/merge_request.graphql';
+import { mrStates, humanMRStates } from '../constants';
+
+export default {
+ name: 'MRPopover',
+ components: {
+ GlPopover,
+ GlSkeletonLoading,
+ Icon,
+ CiIcon,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ target: {
+ type: HTMLAnchorElement,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ mergeRequestIID: {
+ type: String,
+ required: true,
+ },
+ mergeRequestTitle: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ mergeRequest: {},
+ };
+ },
+ computed: {
+ detailedStatus() {
+ return this.mergeRequest.headPipeline && this.mergeRequest.headPipeline.detailedStatus;
+ },
+ formattedTime() {
+ return this.timeFormated(this.mergeRequest.createdAt);
+ },
+ statusBoxClass() {
+ switch (this.mergeRequest.state) {
+ case mrStates.merged:
+ return 'status-box-mr-merged';
+ case mrStates.closed:
+ return 'status-box-closed';
+ default:
+ return 'status-box-open';
+ }
+ },
+ stateHumanName() {
+ switch (this.mergeRequest.state) {
+ case mrStates.merged:
+ return humanMRStates.merged;
+ case mrStates.closed:
+ return humanMRStates.closed;
+ default:
+ return humanMRStates.open;
+ }
+ },
+ showDetails() {
+ return Object.keys(this.mergeRequest).length > 0;
+ },
+ },
+ apollo: {
+ mergeRequest: {
+ query,
+ update: data => data.project.mergeRequest,
+ variables() {
+ const { projectPath, mergeRequestIID } = this;
+
+ return {
+ projectPath,
+ mergeRequestIID,
+ };
+ },
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover :target="target" boundary="viewport" placement="top" show>
+ <div class="mr-popover">
+ <div v-if="$apollo.loading">
+ <gl-skeleton-loading :lines="1" class="animation-container-small mt-1" />
+ </div>
+ <div v-else-if="showDetails" class="d-flex align-items-center justify-content-between">
+ <div class="d-inline-flex align-items-center">
+ <div :class="`issuable-status-box status-box ${statusBoxClass}`">
+ {{ stateHumanName }}
+ </div>
+ <span class="text-secondary">Opened <time v-text="formattedTime"></time></span>
+ </div>
+ <ci-icon v-if="detailedStatus" :status="detailedStatus" />
+ </div>
+ <h5 class="my-2">{{ mergeRequestTitle }}</h5>
+ <div class="text-secondary">
+ {{ `${projectPath}!${mergeRequestIID}` }}
+ </div>
+ </div>
+ </gl-popover>
+</template>
diff --git a/app/assets/javascripts/mr_popover/constants.js b/app/assets/javascripts/mr_popover/constants.js
new file mode 100644
index 00000000000..433df844c80
--- /dev/null
+++ b/app/assets/javascripts/mr_popover/constants.js
@@ -0,0 +1,10 @@
+export const mrStates = {
+ merged: 'merged',
+ closed: 'closed',
+};
+
+export const humanMRStates = {
+ merged: 'Merged',
+ closed: 'Closed',
+ open: 'Open',
+};
diff --git a/app/assets/javascripts/mr_popover/index.js b/app/assets/javascripts/mr_popover/index.js
new file mode 100644
index 00000000000..cc686b401d2
--- /dev/null
+++ b/app/assets/javascripts/mr_popover/index.js
@@ -0,0 +1,62 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import MRPopover from './components/mr_popover.vue';
+import createDefaultClient from '~/lib/graphql';
+
+let renderedPopover;
+let renderFn;
+
+const handleUserPopoverMouseOut = ({ target }) => {
+ target.removeEventListener('mouseleave', handleUserPopoverMouseOut);
+
+ if (renderFn) {
+ clearTimeout(renderFn);
+ }
+ if (renderedPopover) {
+ renderedPopover.$destroy();
+ renderedPopover = null;
+ }
+};
+
+/**
+ * Adds a MergeRequestPopover component to the body, hands over as much data as the target element has in data attributes.
+ * loads based on data-project-path and data-iid more data about an MR from the API and sets it on the popover
+ */
+const handleMRPopoverMount = apolloProvider => ({ target }) => {
+ // Add listener to actually remove it again
+ target.addEventListener('mouseleave', handleUserPopoverMouseOut);
+
+ const { projectPath, mrTitle, iid } = target.dataset;
+ const mergeRequest = {};
+
+ renderFn = setTimeout(() => {
+ const MRPopoverComponent = Vue.extend(MRPopover);
+ renderedPopover = new MRPopoverComponent({
+ propsData: {
+ target,
+ projectPath,
+ mergeRequestIID: iid,
+ mergeRequest,
+ mergeRequestTitle: mrTitle,
+ },
+ apolloProvider,
+ });
+
+ renderedPopover.$mount();
+ }, 200); // 200ms delay so not every mouseover triggers Popover + API Call
+};
+
+export default elements => {
+ const mrLinks = elements || [...document.querySelectorAll('.gfm-merge_request')];
+ if (mrLinks.length > 0) {
+ Vue.use(VueApollo);
+
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
+
+ mrLinks.forEach(el => {
+ el.addEventListener('mouseenter', handleMRPopoverMount(apolloProvider));
+ });
+ }
+};
diff --git a/app/assets/javascripts/mr_popover/queries/merge_request.graphql b/app/assets/javascripts/mr_popover/queries/merge_request.graphql
new file mode 100644
index 00000000000..0bb9bc03bc7
--- /dev/null
+++ b/app/assets/javascripts/mr_popover/queries/merge_request.graphql
@@ -0,0 +1,14 @@
+query mergeRequest($projectPath: ID!, $mergeRequestIID: ID!) {
+ project(fullPath: $projectPath) {
+ mergeRequest(iid: $mergeRequestIID) {
+ createdAt
+ state
+ headPipeline {
+ detailedStatus {
+ icon
+ group
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue
index c5ae7e7ee10..b59ddd0d57a 100644
--- a/app/assets/javascripts/notebook/cells/output/index.vue
+++ b/app/assets/javascripts/notebook/cells/output/index.vue
@@ -20,12 +20,20 @@ export default {
required: true,
},
},
- data() {
- return {
- outputType: '',
- };
- },
methods: {
+ outputType(output) {
+ if (output.text) {
+ return 'text/plain';
+ } else if (output.data['image/png']) {
+ return 'image/png';
+ } else if (output.data['text/html']) {
+ return 'text/html';
+ } else if (output.data['image/svg+xml']) {
+ return 'image/svg+xml';
+ }
+
+ return 'text/plain';
+ },
dataForType(output, type) {
let data = output.data[type];
@@ -39,20 +47,13 @@ export default {
if (output.text) {
return CodeOutput;
} else if (output.data['image/png']) {
- this.outputType = 'image/png';
-
return ImageOutput;
} else if (output.data['text/html']) {
- this.outputType = 'text/html';
-
return HtmlOutput;
} else if (output.data['image/svg+xml']) {
- this.outputType = 'image/svg+xml';
-
return HtmlOutput;
}
- this.outputType = 'text/plain';
return CodeOutput;
},
rawCode(output) {
@@ -60,7 +61,7 @@ export default {
return output.text.join('');
}
- return this.dataForType(output, this.outputType);
+ return this.dataForType(output, this.outputType(output));
},
},
};
@@ -73,7 +74,7 @@ export default {
v-for="(output, index) in outputs"
:key="index"
type="output"
- :output-type="outputType"
+ :output-type="outputType(output)"
:count="count"
:index="index"
:raw-code="rawCode(output)"
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index d8947e8ca50..ab758a9e922 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -72,8 +72,8 @@ export default {
:can-current-user-fork="false"
:expanded="!discussion.diff_file.viewer.collapsed"
/>
- <div v-if="isTextFile" :class="$options.userColorSchemeClass" class="diff-content code">
- <table>
+ <div v-if="isTextFile" class="diff-content">
+ <table class="code js-syntax-highlight" :class="$options.userColorSchemeClass">
<template v-if="hasTruncatedDiffLines">
<tr
v-for="line in discussion.truncated_diff_lines"
@@ -81,8 +81,8 @@ export default {
:key="line.line_code"
class="line_holder"
>
- <td class="diff-line-num old_line">{{ line.old_line }}</td>
- <td class="diff-line-num new_line">{{ line.new_line }}</td>
+ <td :class="line.type" class="diff-line-num old_line">{{ line.old_line }}</td>
+ <td :class="line.type" class="diff-line-num new_line">{{ line.new_line }}</td>
<td :class="line.type" class="line_content" v-html="line.rich_text"></td>
</tr>
</template>
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index fb1d98355b3..ff303d0f55a 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -95,7 +95,6 @@ export default {
<div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body">
<suggestions
v-if="hasSuggestion && !isEditing"
- class="note-text md"
:suggestions="note.suggestions"
:note-html="note.note_html"
:line-type="lineType"
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 0fabbfb06b5..a3d664a738f 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -87,13 +87,10 @@ export default {
},
},
data() {
- const { diff_discussion: isDiffDiscussion, resolved } = this.discussion;
-
return {
isReplying: false,
isResolving: false,
resolveAsThread: true,
- isRepliesCollapsed: Boolean(!isDiffDiscussion && resolved),
};
},
computed: {
@@ -178,11 +175,11 @@ export default {
return '';
},
- shouldShowDiscussions() {
- const { expanded, resolved } = this.discussion;
- const isResolvedNonDiffDiscussion = !this.discussion.diff_discussion && resolved;
-
- return expanded || this.alwaysExpanded || isResolvedNonDiffDiscussion;
+ isExpanded() {
+ return this.discussion.expanded || this.alwaysExpanded;
+ },
+ shouldHideDiscussionBody() {
+ return this.shouldRenderDiffs && !this.isExpanded;
},
actionText() {
const linkStart = `<a href="${_.escape(this.discussion.discussion_path)}">`;
@@ -282,9 +279,6 @@ export default {
toggleDiscussionHandler() {
this.toggleDiscussion({ discussionId: this.discussion.id });
},
- toggleReplies() {
- this.isRepliesCollapsed = !this.isRepliesCollapsed;
- },
showReplyForm() {
this.isReplying = true;
},
@@ -405,7 +399,7 @@ Please check your network connection and try again.`;
/>
</div>
</div>
- <div v-if="shouldShowDiscussions" class="discussion-body">
+ <div v-if="!shouldHideDiscussionBody" class="discussion-body">
<component
:is="wrapperComponent"
v-bind="wrapperComponentProps"
@@ -436,11 +430,11 @@ Please check your network connection and try again.`;
</component>
<toggle-replies-widget
v-if="hasReplies"
- :collapsed="isRepliesCollapsed"
+ :collapsed="!isExpanded"
:replies="replies"
- @toggle="toggleReplies"
+ @toggle="toggleDiscussionHandler"
/>
- <template v-if="!isRepliesCollapsed">
+ <template v-if="isExpanded">
<component
:is="componentName(note)"
v-for="note in replies"
@@ -467,7 +461,7 @@ Please check your network connection and try again.`;
</template>
</ul>
<div
- v-if="!isRepliesCollapsed || !hasReplies"
+ v-if="isExpanded || !hasReplies"
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder"
>
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index e7fa05faa8a..6aed2492084 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -4,6 +4,7 @@ import Flash from './flash';
export default function notificationsDropdown() {
$(document).on('click', '.update-notification', function updateNotificationCallback(e) {
e.preventDefault();
+
if ($(this).is('.is-active') && $(this).data('notificationLevel') === 'custom') {
return;
}
diff --git a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
index 9177943f88a..dd79ade5bc9 100644
--- a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
+++ b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
@@ -1,6 +1,16 @@
+import Flash from '~/flash';
+import { __ } from '~/locale';
+
export default {
methods: {
clickTriggeredByPipeline() {},
clickTriggeredPipeline() {},
+ requestRefreshPipelineGraph() {
+ // When an action is clicked
+ // (wether in the dropdown or in the main nodes, we refresh the big graph)
+ this.mediator
+ .refreshPipeline()
+ .catch(() => Flash(__('An error occurred while making the request.')));
+ },
},
};
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index 8adbd39edd4..6660f8120f8 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -29,15 +29,6 @@ export default () => {
mediator,
};
},
- methods: {
- requestRefreshPipelineGraph() {
- // When an action is clicked
- // (wether in the dropdown or in the main nodes, we refresh the big graph)
- this.mediator
- .refreshPipeline()
- .catch(() => Flash(__('An error occurred while making the request.')));
- },
- },
render(createElement) {
return createElement('pipeline-graph', {
props: {
diff --git a/app/assets/javascripts/pipelines/stores/pipeline_store.js b/app/assets/javascripts/pipelines/stores/pipeline_store.js
index 052e34a8aef..259278b6410 100644
--- a/app/assets/javascripts/pipelines/stores/pipeline_store.js
+++ b/app/assets/javascripts/pipelines/stores/pipeline_store.js
@@ -1,7 +1,6 @@
export default class PipelineStore {
constructor() {
this.state = {};
-
this.state.pipeline = {};
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
index 2a4dff71d9b..11bc8c73ee9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
@@ -80,7 +80,7 @@ export default {
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
<span class="bold">
- <span v-if="mr.mergeError" class="has-error-message"> {{ mergeError }}. </span>
+ <span v-if="mr.mergeError" class="has-error-message"> {{ mergeError }} </span>
<span v-else> {{ s__('mrWidget|Merge failed.') }} </span>
<span :class="{ 'has-custom-error': mr.mergeError }"> {{ timerText }} </span>
</span>
diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue
index e6f0a1c69cd..25f80219993 100644
--- a/app/assets/javascripts/vue_shared/components/ci_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue
@@ -22,6 +22,7 @@ import Icon from '../../vue_shared/components/icon.vue';
* - Jobs show view header
* - Jobs show view sidebar
* - Linked pipelines
+ * - Extended MR Popover
*/
const validSizes = [8, 12, 16, 18, 24, 32, 48, 72];
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
index c9915f7d685..5fdc915fffb 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue
@@ -78,8 +78,8 @@ export default {
</script>
<template>
- <div ref="markdown-preview" class="md md-previewer">
+ <div ref="markdown-preview" class="md-previewer">
<gl-skeleton-loading v-if="isLoading" />
- <div v-else v-html="previewContent"></div>
+ <div v-else class="md" v-html="previewContent"></div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 3f607aa2a0a..eccf73e227c 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -189,7 +189,7 @@ export default {
<div
ref="gl-form"
:class="{ 'prepend-top-default append-bottom-default': addSpacingClasses }"
- class="md-area js-vue-markdown-field"
+ class="js-vue-markdown-field md-area position-relative"
>
<markdown-header
:preview-markdown="previewMarkdown"
@@ -215,7 +215,7 @@ export default {
<div
v-show="previewMarkdown"
ref="markdown-preview"
- class="md-preview js-vue-md-preview md md-preview-holder"
+ class="js-vue-md-preview md-preview-holder"
>
<suggestions
v-if="hasSuggestion"
@@ -233,7 +233,7 @@ export default {
<div
v-show="previewMarkdown"
ref="markdown-preview"
- class="md-preview js-vue-md-preview md md-preview-holder"
+ class="js-vue-md-preview md md-preview-holder"
v-html="markdownPreview"
></div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index dcda701f049..177d78cb904 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -130,6 +130,6 @@ export default {
<template>
<div>
<div class="flash-container js-suggestions-flash"></div>
- <div v-show="isRendered" ref="container" v-html="noteHtml"></div>
+ <div v-show="isRendered" ref="container" class="md" v-html="noteHtml"></div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
index 8d3a3009c55..a50f49c1279 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -57,7 +57,7 @@ export default {
</div>
</div>
<div class="note-body">
- <div class="note-text">
+ <div class="note-text md">
<p>{{ note.body }}</p>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index b0af8399955..acc179b3834 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -93,7 +93,7 @@ export default {
'system-note-commit-list': hasMoreCommits,
'hide-shade': expanded,
}"
- class="note-text"
+ class="note-text md"
v-html="note.note_html"
></div>
<div v-if="hasMoreCommits" class="flex-list">
diff --git a/app/assets/javascripts/vue_shared/components/svg_gradient.vue b/app/assets/javascripts/vue_shared/components/svg_gradient.vue
index cca90af275e..5ce45d492f9 100644
--- a/app/assets/javascripts/vue_shared/components/svg_gradient.vue
+++ b/app/assets/javascripts/vue_shared/components/svg_gradient.vue
@@ -4,10 +4,16 @@ export default {
colors: {
type: Array,
required: true,
+ validator(value) {
+ return value.length === 2;
+ },
},
opacity: {
type: Array,
required: true,
+ validator(value) {
+ return value.length === 2;
+ },
},
identifierName: {
type: String,
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index c8357f7751c..93377b8dd91 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -343,16 +343,6 @@ input[type=color].form-control {
}
}
-// Bootstrap 3 compatibility because bootstrap_form Gem is not updated yet
-.input-group-btn:first-child {
- @extend .input-group-prepend;
-}
-
-// Bootstrap 3 compatibility because bootstrap_form Gem is not updated yet
-.input-group-btn:last-child {
- @extend .input-group-append;
-}
-
/*
Bootstrap 4.1.2 introduced a new default vertical alignment which breaks our icons,
so we need to reset the vertical alignment to the default value. See:
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
index 2f4d30fe923..7d46b262a69 100644
--- a/app/assets/stylesheets/components/popover.scss
+++ b/app/assets/stylesheets/components/popover.scss
@@ -7,3 +7,10 @@
line-height: $gl-line-height;
}
}
+
+.mr-popover {
+ .text-secondary {
+ font-size: 12px;
+ line-height: 1.33;
+ }
+}
diff --git a/app/assets/stylesheets/components/related_items_list.scss b/app/assets/stylesheets/components/related_items_list.scss
index edf7b26ebaa..5a5601f2fa3 100644
--- a/app/assets/stylesheets/components/related_items_list.scss
+++ b/app/assets/stylesheets/components/related_items_list.scss
@@ -307,6 +307,7 @@ $item-weight-max-width: 48px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
+ line-height: 1.3;
}
.issue-token-state-icon-open,
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 216877a4461..ab9047c54e4 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -60,6 +60,7 @@
@import 'framework/memory_graph';
@import 'framework/responsive_tables';
@import 'framework/stacked_progress_bar';
+@import 'framework/sortable';
@import 'framework/ci_variable_list';
@import 'framework/feature_highlight';
@import 'framework/terms';
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 97a9a55c968..d72597a6147 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -415,6 +415,12 @@ img.emoji {
.ws-normal { white-space: normal; }
.overflow-auto { overflow: auto; }
+.d-flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
/** COMMON SIZING CLASSES **/
.w-0 { width: 0; }
@@ -456,10 +462,10 @@ img.emoji {
}
/** COMMON POSITIONING CLASSES */
-.position-bottom-0 { bottom: 0; }
-.position-left-0 { left: 0; }
-.position-right-0 { right: 0; }
-.position-top-0 { top: 0; }
+.position-bottom-0 { bottom: 0 !important; }
+.position-left-0 { left: 0 !important; }
+.position-right-0 { right: 0 !important; }
+.position-top-0 { top: 0 !important; }
.drag-handle {
width: 4px;
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 6108eaa1ad0..8d38310e8e6 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -25,10 +25,6 @@
}
}
- table {
- @extend .table;
- }
-
.file-title {
position: relative;
background-color: $gray-light;
@@ -123,7 +119,7 @@
}
}
- &.wiki {
+ &.md {
padding: $gl-padding;
@include media-breakpoint-up(md) {
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 3b1d1d67509..c36c15a85be 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -128,7 +128,7 @@ label {
.form-control {
@include box-shadow(none);
- border-radius: 2px;
+ border-radius: $border-radius-default;
padding: $gl-vert-padding $gl-input-padding;
&.input-short {
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index de2cd600623..7c10de828cd 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -50,10 +50,6 @@
transition: opacity 200ms ease-in-out;
}
-.md-area {
- position: relative;
-}
-
.md-header {
.nav-links {
a {
@@ -135,30 +131,6 @@
width: 100%;
}
-.md:not(.use-csslab) {
- &.md-preview-holder {
- // Reset ul style types since we're nested inside a ul already
- @include bulleted-list;
- }
-
- // On diffs code should wrap nicely and not overflow
- code {
- white-space: pre-wrap;
- word-break: keep-all;
- }
-
- hr {
- // Darken 'whitesmoke' a bit to make it more visible in note bodies
- border-color: darken($gray-normal, 8%);
- margin: 10px 0;
- }
-
-
- table {
- @include markdown-table;
- }
-}
-
.toolbar-btn {
float: left;
padding: 0 7px;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 9e192cbe3fc..18eb10c1f23 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -22,26 +22,6 @@
}
/*
- * Mixin for markdown tables
- */
-@mixin markdown-table {
- width: auto;
- display: block;
- overflow-x: auto;
- border: 0;
-
- tr {
- th {
- border-bottom: solid 2px $gl-gray-200;
- }
-
- td {
- border-color: $gl-gray-200;
- }
- }
-}
-
-/*
* Base mixin for lists in GitLab
*/
@mixin basic-list {
@@ -93,20 +73,6 @@
}
}
-@mixin bulleted-list {
- > ul {
- list-style-type: disc;
-
- ul {
- list-style-type: circle;
-
- ul {
- list-style-type: square;
- }
- }
- }
-}
-
@mixin webkit-prefix($property, $value) {
#{'-webkit-' + $property}: $value;
#{$property}: $value;
diff --git a/app/assets/stylesheets/framework/page_title.scss b/app/assets/stylesheets/framework/page_title.scss
index e8302953a63..ad6575761b5 100644
--- a/app/assets/stylesheets/framework/page_title.scss
+++ b/app/assets/stylesheets/framework/page_title.scss
@@ -2,7 +2,6 @@
@extend .d-flex;
@extend .align-items-center;
- padding-top: $gl-padding-top;
border-bottom: 1px solid $border-color;
.page-title {
diff --git a/app/assets/stylesheets/framework/sortable.scss b/app/assets/stylesheets/framework/sortable.scss
new file mode 100644
index 00000000000..8c070200135
--- /dev/null
+++ b/app/assets/stylesheets/framework/sortable.scss
@@ -0,0 +1,92 @@
+.sortable-container {
+ background-color: $gray-light;
+
+ .flex-list {
+ padding: 5px;
+ margin-bottom: 0;
+ }
+}
+
+.sortable-row {
+ .flex-row {
+ display: flex;
+
+ &.issuable-info-container {
+ padding-right: 0;
+ }
+ }
+
+ .sortable-link {
+ color: $black;
+ }
+}
+
+.gl-sortable {
+ .header {
+ user-select: none;
+
+ &:hover {
+ cursor: pointer;
+ background-color: $gray-100;
+ }
+
+ &:focus {
+ outline: 1px solid $blue-300;
+ }
+ }
+}
+
+.related-issues-list-item {
+ .card-body,
+ .issuable-info-container {
+ padding: $gl-padding-4 $gl-padding-4 $gl-padding-4 $gl-padding;
+
+ .block-truncated {
+ padding: $gl-padding-8 0;
+ line-height: $gl-btn-line-height;
+ }
+
+ @include media-breakpoint-down(md) {
+ padding-left: $gl-padding;
+
+ .block-truncated {
+ flex-direction: column-reverse;
+ padding: $gl-padding-4 0;
+
+ .text-secondary {
+ margin-top: $gl-padding-4;
+ }
+
+ .issue-token-title-text {
+ display: block;
+ }
+ }
+
+ .issue-item-remove-button {
+ align-self: baseline;
+ }
+ }
+
+ @include media-breakpoint-only(md) {
+ .block-truncated .issue-token-title-text {
+ white-space: nowrap;
+ }
+
+ .issue-item-remove-button {
+ align-self: center;
+ }
+ }
+
+ @include media-breakpoint-down(sm) {
+ padding-left: $gl-padding-8;
+
+ .block-truncated .issue-token-title-text {
+ white-space: normal;
+ }
+ }
+ }
+
+ &.is-dragging {
+ padding: 0;
+ }
+}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 5e5e8bcc3d6..244b414d334 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -1,4 +1,8 @@
-@mixin md-typography {
+/**
+ * Apply Markdown typography
+ *
+ */
+.md:not(.use-csslab) {
color: $gl-text-color;
word-wrap: break-word;
@@ -6,8 +10,35 @@
text-align: initial;
}
+ *:first-child {
+ margin-top: 0;
+ }
+
+ > :last-child {
+ margin-bottom: 0;
+ }
+
+ p {
+ color: $gl-text-color;
+ margin: 0 0 16px;
+
+ > code {
+ font-weight: inherit;
+ }
+
+ a:not(.no-attachment-icon) img {
+ // Remove bottom padding because
+ // <p> already has $gl-padding bottom
+ margin-bottom: 0;
+ }
+ }
+
a {
color: $blue-600;
+
+ > code {
+ color: $blue-600;
+ }
}
img:not(.emoji) {
@@ -28,18 +59,12 @@
max-width: 100%;
}
- p a:not(.no-attachment-icon) img {
- // Remove bottom padding because
- // <p> already has $gl-padding bottom
- margin-bottom: 0;
- }
-
- *:first-child {
- margin-top: 0;
- }
-
- > :last-child {
- margin-bottom: 0;
+ &:not(.md-file) img:not(.emoji) {
+ border: 1px solid $white-normal;
+ padding: 5px;
+ margin: 5px 0;
+ // Ensure that image does not exceed viewport
+ max-height: calc(100vh - 100px);
}
// Single code lines should wrap
@@ -47,6 +72,7 @@
font-family: $monospace-font;
white-space: pre-wrap;
word-wrap: normal;
+ word-break: keep-all;
}
kbd {
@@ -131,17 +157,25 @@
}
}
- p {
- color: $gl-text-color;
- margin: 0 0 16px;
+ hr {
+ // Darken 'whitesmoke' a bit to make it more visible in note bodies
+ border-color: darken($gray-normal, 8%);
+ margin: 10px 0;
}
- table {
+ table:not(.code) {
@extend .table;
@extend .table-bordered;
margin: 16px 0;
color: $gl-text-color;
border: 0;
+ width: auto;
+ display: block;
+ overflow-x: auto;
+
+ tbody {
+ background-color: $white-light;
+ }
tr {
th {
@@ -179,14 +213,6 @@
}
}
- p > code {
- font-weight: inherit;
- }
-
- a > code {
- color: $blue-600;
- }
-
dd {
margin-left: $gl-padding;
}
@@ -202,6 +228,18 @@
margin: 3px 28px 3px 0 !important;
}
+ > ul {
+ list-style-type: disc;
+
+ ul {
+ list-style-type: circle;
+
+ ul {
+ list-style-type: square;
+ }
+ }
+ }
+
li {
line-height: 1.6em;
margin-left: 25px;
@@ -245,11 +283,11 @@
&:hover::before {
text-decoration: none;
}
- }
- a.no-attachment-icon {
- &::before {
- display: none;
+ &.no-attachment-icon {
+ &::before {
+ display: none;
+ }
}
}
@@ -368,28 +406,6 @@ code {
}
/**
- * Apply Markdown typography
- *
- */
-.wiki:not(.use-csslab) {
- @include md-typography;
-}
-
-.md:not(.use-csslab) {
- @include md-typography;
-
- &:not(.wiki) {
- img:not(.emoji) {
- border: 1px solid $white-normal;
- padding: 5px;
- margin: 5px 0;
- // Ensure that image does not exceed viewport
- max-height: calc(100vh - 100px);
- }
- }
-}
-
-/**
* Textareas intended for GFM
*
*/
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index efebbd124d0..5d4c84c494d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -711,3 +711,11 @@ $mr-version-controls-height: 56px;
Compare Branches
*/
$compare-branches-sticky-header-height: 68px;
+
+/**
+ Bootstrap 4.2.0 introduced new icons for validating forms.
+ Our design system does not use those, so we are disabling them for now:
+ - Docs: https://getbootstrap.com/docs/4.3/components/forms/#server-side
+ - Issue: https://gitlab.com/gitlab-org/design.gitlab.com/issues/242
+ */
+$enable-validation-icons: false;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index efcc437bd7f..fb4d3f23cd9 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -9,7 +9,6 @@ $input-border-color: $gray-200;
$input-color: $gl-text-color;
$font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font;
-$input-line-height: 20px;
$btn-line-height: 20px;
$table-accent-bg: $gray-light;
$card-border-color: $border-color;
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index 161943766d4..434cbd6d21c 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -12,6 +12,10 @@
border-bottom: 1px solid $well-inner-border;
}
+ &.borderless {
+ border-bottom: 0;
+ }
+
&.branch-info {
.commit-sha,
.commit-info {
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index f24c80bd81c..d77b7dfad68 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -1,4 +1,4 @@
-@import "framework/variables";
+@import 'framework/variables';
img {
max-width: 100%;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 81216b2b98e..ed0e9db035b 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -19,6 +19,7 @@
.is-ghost {
opacity: 0.3;
+ pointer-events: none;
}
.dropdown-projects {
@@ -50,6 +51,19 @@
.content-wrapper {
padding-bottom: 0;
}
+
+ .issues-details-filters {
+ display: flex;
+ }
+
+ .filter-form {
+ width: 100%;
+ }
+}
+
+.board-extra-actions {
+ font-size: 0;
+ white-space: nowrap;
}
.boards-app {
@@ -236,7 +250,8 @@
}
}
-.board-blank-state {
+.board-blank-state,
+.board-promotion-state {
padding: $gl-padding;
background-color: $white-light;
flex: 1;
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index c88922ae5ea..1fedbd8bddb 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -89,138 +89,6 @@
}
}
- .note-text {
- table {
- font-family: $font-family-sans-serif;
- }
- }
-
- table {
- width: 100%;
- font-family: $monospace-font;
- border: 0;
- border-collapse: separate;
- margin: 0;
- padding: 0;
- table-layout: fixed;
- border-radius: 0 0 $border-radius-default $border-radius-default;
-
- .diff-line-num {
- width: 50px;
- position: relative;
-
- a {
- transition: none;
- }
- }
-
- .line_holder td {
- line-height: $code-line-height;
- font-size: $code-font-size;
- vertical-align: top;
-
- &.noteable_line {
- position: relative;
- }
-
- span {
- white-space: pre-wrap;
-
- &.context-cell {
- display: inline-block;
- width: 100%;
- height: 100%;
- }
- }
-
- .line {
- word-wrap: break-word;
- }
- }
-
- &.left-side-selected {
- td.line_content.parallel.right-side {
- user-select: none;
- }
- }
-
- &.right-side-selected {
- td.line_content.parallel.left-side {
- user-select: none;
- }
- }
- }
-
- tr.line_holder.parallel {
- td.line_content.parallel {
- width: 46%;
- }
-
- .add-diff-note {
- margin-left: -55px;
- }
- }
-
- .old_line,
- .new_line {
- user-select: none;
- margin: 0;
- border: 0;
- padding: 0 5px;
- border-right: 1px solid;
- text-align: right;
- min-width: 35px;
- max-width: 50px;
- width: 35px;
-
- a {
- float: left;
- width: 35px;
- font-weight: $gl-font-weight-normal;
-
- &[disabled] {
- cursor: default;
-
- &:hover,
- &:active {
- text-decoration: none;
- }
- }
- }
- }
-
- .line_content {
- display: block;
- margin: 0;
- padding: 0 1.5em;
- border: 0;
- position: relative;
-
- &.parallel {
- display: table-cell;
-
- span {
- word-break: break-all;
- }
- }
-
- &.old {
- &::before {
- content: '-';
- position: absolute;
- left: 0.5em;
- }
- }
-
- &.new {
- &::before {
- content: '+';
- position: absolute;
- left: 0.5em;
- }
- }
- }
-
.diff-loading-error-block {
padding: $gl-padding * 2 $gl-padding;
text-align: center;
@@ -443,10 +311,6 @@
}
}
- .line_content {
- white-space: pre-wrap;
- }
-
.diff-file-container {
.frame.deleted {
border: 1px solid $deleted;
@@ -508,6 +372,114 @@
}
}
+table.code {
+ width: 100%;
+ font-family: $monospace-font;
+ border: 0;
+ border-collapse: separate;
+ margin: 0;
+ padding: 0;
+ table-layout: fixed;
+ border-radius: 0 0 $border-radius-default $border-radius-default;
+
+ tr.line_holder td {
+ line-height: $code-line-height;
+ font-size: $code-font-size;
+ vertical-align: top;
+
+ span {
+ white-space: pre-wrap;
+
+ &.context-cell {
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ }
+
+ &.line {
+ word-wrap: break-word;
+ }
+ }
+
+ &.diff-line-num {
+ user-select: none;
+ margin: 0;
+ border: 0;
+ padding: 0 10px 0 5px;
+ border-right: 1px solid;
+ text-align: right;
+ width: 50px;
+ position: relative;
+
+ a {
+ transition: none;
+ float: left;
+ width: 100%;
+ font-weight: $gl-font-weight-normal;
+
+ &[disabled] {
+ cursor: default;
+
+ &:hover,
+ &:active {
+ text-decoration: none;
+ }
+ }
+ }
+
+ &:not(.js-unfold-bottom) a::before {
+ content: attr(data-linenumber);
+ }
+ }
+
+ &.line_content {
+ display: block;
+ margin: 0;
+ padding: 0 1.5em;
+ border: 0;
+ position: relative;
+ white-space: pre-wrap;
+
+ &.parallel {
+ display: table-cell;
+ width: 46%;
+
+ span {
+ word-break: break-all;
+ }
+ }
+
+ &.old {
+ &::before {
+ content: '-';
+ position: absolute;
+ left: 0.5em;
+ }
+ }
+
+ &.new {
+ &::before {
+ content: '+';
+ position: absolute;
+ left: 0.5em;
+ }
+ }
+ }
+ }
+
+ &.left-side-selected {
+ td.line_content.parallel.right-side {
+ user-select: none;
+ }
+ }
+
+ &.right-side-selected {
+ td.line_content.parallel.left-side {
+ user-select: none;
+ }
+ }
+}
+
.diff-stats {
align-items: center;
padding: 0 0.25rem;
@@ -608,16 +580,6 @@
}
}
-.file-holder {
- .diff-line-num:not(.js-unfold-bottom) {
- a {
- &::before {
- content: attr(data-linenumber);
- }
- }
- }
-}
-
.diff-comment-avatar-holders {
position: absolute;
height: 19px;
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 61ecf133b02..0eb854ecf98 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -346,7 +346,9 @@
}
> .popover-title,
- > .popover-content {
+ > .popover-content,
+ > .popover-header,
+ > .popover-body {
padding: 8px;
font-size: 12px;
white-space: nowrap;
diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss
index 161d4dbfb22..7610c5cf6f3 100644
--- a/app/assets/stylesheets/pages/help.scss
+++ b/app/assets/stylesheets/pages/help.scss
@@ -37,12 +37,4 @@
.documentation {
padding: 7px;
-
- // Border around images in the help pages.
- img:not(.emoji) {
- border: 1px solid $white-normal;
- padding: 5px;
- margin: 5px;
- max-height: calc(100vh - 100px);
- }
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 623fa485ba6..6415d902ca6 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -72,14 +72,6 @@
height: $gl-padding * 2;
}
- // Border around images in issue and MR descriptions.
- .description img:not(.emoji) {
- border: 1px solid $white-normal;
- padding: 5px;
- max-height: calc(100vh - 100px);
- max-width: 100%;
- }
-
.emoji-block {
padding: 10px 0;
}
@@ -264,6 +256,10 @@
.selectbox {
display: none;
+
+ &.show {
+ display: block;
+ }
}
.btn-clipboard:hover {
@@ -317,6 +313,7 @@
}
.no-value,
+ .btn-default-hover-link,
.btn-secondary-hover-link {
color: $gl-text-color-secondary;
}
@@ -600,7 +597,6 @@
margin: -7px;
}
-
.user-list {
display: flex;
flex-wrap: wrap;
@@ -724,6 +720,7 @@
.issuable-main-info {
flex: 1 auto;
margin-right: 10px;
+ min-width: 0;
.issue-weight-icon {
vertical-align: sub;
@@ -785,6 +782,7 @@
@media(max-width: map-get($grid-breakpoints, lg)-1) {
.task-status,
.issuable-due-date,
+ .issuable-weight,
.project-ref-path {
display: none;
}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 9f30495a7ef..c7d2369a6b8 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -145,6 +145,11 @@ ul.related-merge-requests > li {
}
}
+.issues-footer {
+ padding-top: $gl-padding;
+ padding-bottom: 37px;
+}
+
.issues-nav-controls {
font-size: 0;
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index eb32beb0972..e0b84e0f92d 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -14,6 +14,12 @@
}
.member {
+ &.is-overriden {
+ .btn-ldap-override {
+ display: none !important;
+ }
+ }
+
.list-item-name {
@include media-breakpoint-up(sm) {
float: left;
@@ -122,6 +128,49 @@
outline: 0;
}
+.members-ldap {
+ align-self: center;
+ height: 100%;
+ margin-right: 10px;
+ margin-left: -49px;
+}
+
+.alert-member-ldap {
+ background-color: $orange-50;
+
+ @include media-breakpoint-up(sm) {
+ line-height: 40px;
+ }
+
+ > p {
+ float: left;
+ margin-bottom: 10px;
+ color: $orange-600;
+
+ @include media-breakpoint-up(sm) {
+ padding-left: 55px;
+ margin-bottom: 0;
+ }
+ }
+
+ .controls {
+ width: 100%;
+
+ @include media-breakpoint-up(sm) {
+ width: auto;
+ }
+ }
+}
+
+.btn-ldap-override {
+ width: 100%;
+
+ @include media-breakpoint-up(sm) {
+ margin-left: 10px;
+ width: auto;
+ }
+}
+
.flex-project-members-panel {
display: flex;
flex-direction: row;
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 3ca8e943a3a..49608a3964f 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -235,6 +235,7 @@ $status-box-line-height: 26px;
padding: 0;
}
+ .popover-body,
.popover-content {
padding: 0;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 9c72dcbc54c..0c334e919de 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -225,8 +225,6 @@ $note-form-margin-left: 72px;
overflow-y: hidden;
.note-text {
- // Reset ul style types since we're nested inside a ul already
- @include bulleted-list;
word-wrap: break-word;
}
}
@@ -738,7 +736,7 @@ $note-form-margin-left: 72px;
.add-diff-note {
@include btn-comment-icon;
opacity: 0;
- margin-left: -55px;
+ margin-left: -50px;
position: absolute;
top: 50%;
transform: translateY(-50%);
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 8e933b62dd9..87cef43b923 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -305,6 +305,7 @@ table.u2f-registrations {
margin: 20px -5px 0;
.bordered-box {
+ padding: 32px;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
background-color: $blue-50;
@@ -438,8 +439,17 @@ table.u2f-registrations {
}
}
+ .form-group > label {
+ font-weight: $gl-font-weight-bold;
+ }
+
+ .form-group > .form-text {
+ font-size: $gl-font-size;
+ }
+
.emoji-menu-toggle-button {
@include emoji-menu-toggle-button;
+ padding: 6px 10px;
.no-emoji-placeholder {
position: relative;
@@ -461,3 +471,41 @@ table.u2f-registrations {
.help-block {
color: $gl-text-color-secondary;
}
+
+.gitlab-slack-gif {
+ width: 100%;
+ max-width: $add-to-slack-gif-max-width;
+}
+
+.gitlab-slack-well {
+ background-color: $white-light;
+ box-shadow: none;
+ max-width: $add-to-slack-well-max-width;
+}
+
+.gitlab-slack-logo {
+ width: $add-to-slack-logo-size;
+ height: $add-to-slack-logo-size;
+}
+
+.gitlab-slack-popup {
+ width: 100%;
+ max-width: $add-to-slack-popup-max-width;
+}
+
+.gitlab-slack-right-arrow svg {
+ fill: $white-dark;
+ width: $right-arrow-size;
+ height: $right-arrow-size;
+ vertical-align: text-bottom;
+}
+
+.gitlab-slack-double-headed-arrow {
+ vertical-align: text-top;
+
+ svg {
+ fill: $gray-darker;
+ width: $double-headed-arrow-width;
+ height: $double-headed-arrow-height;
+ }
+}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 82e887aa62a..3260aed143e 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -179,9 +179,3 @@ ul.wiki-pages-list.content-list {
}
}
}
-
-.wiki:not(.use-csslab) {
- table {
- @include markdown-table;
- }
-}
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
index bb10928a037..9ed1600419d 100644
--- a/app/assets/stylesheets/print.scss
+++ b/app/assets/stylesheets/print.scss
@@ -1,21 +1,21 @@
-.wiki h1,
-.wiki h2,
-.wiki h3,
-.wiki h4,
-.wiki h5,
-.wiki h6 {
+.md h1,
+.md h2,
+.md h3,
+.md h4,
+.md h5,
+.md h6 {
margin-top: 17px;
}
-.wiki h1 {
+.md h1 {
font-size: 30px;
}
-.wiki h2 {
+.md h2 {
font-size: 22px;
}
-.wiki h3 {
+.md h3 {
font-size: 18px;
font-weight: 600;
}
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index 68a2a83f0de..e82756e4643 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -123,25 +123,25 @@ class Clusters::ClustersController < Clusters::BaseController
private
def update_params
- if cluster.managed?
+ if cluster.provided_by_user?
params.require(:cluster).permit(
:enabled,
+ :name,
:environment_scope,
:base_domain,
platform_kubernetes_attributes: [
+ :api_url,
+ :token,
+ :ca_cert,
:namespace
]
)
else
params.require(:cluster).permit(
:enabled,
- :name,
:environment_scope,
:base_domain,
platform_kubernetes_attributes: [
- :api_url,
- :token,
- :ca_cert,
:namespace
]
)
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 5507328f8ae..d5c4712bd78 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -36,7 +36,7 @@ module AuthenticatesWithTwoFactor
end
def locked_user_redirect(user)
- flash.now[:alert] = 'Invalid Login or password'
+ flash.now[:alert] = _('Invalid Login or password')
render 'devise/sessions/new'
end
@@ -66,7 +66,7 @@ module AuthenticatesWithTwoFactor
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
- flash.now[:alert] = 'Invalid two-factor code.'
+ flash.now[:alert] = _('Invalid two-factor code.')
prompt_for_two_factor(user)
end
end
@@ -83,7 +83,7 @@ module AuthenticatesWithTwoFactor
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
- flash.now[:alert] = 'Authentication via U2F device failed.'
+ flash.now[:alert] = _('Authentication via U2F device failed.')
prompt_for_two_factor(user)
end
end
diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb
index b3777fd2b0f..e8e681ce649 100644
--- a/app/controllers/concerns/creates_commit.rb
+++ b/app/controllers/concerns/creates_commit.rb
@@ -31,7 +31,7 @@ module CreatesCommit
respond_to do |format|
format.html { redirect_to success_path }
- format.json { render json: { message: "success", filePath: success_path } }
+ format.json { render json: { message: _("success"), filePath: success_path } }
end
else
flash[:alert] = result[:message]
@@ -45,7 +45,7 @@ module CreatesCommit
redirect_to failure_path
end
end
- format.json { render json: { message: "failed", filePath: failure_path } }
+ format.json { render json: { message: _("failed"), filePath: failure_path } }
end
end
end
@@ -60,15 +60,22 @@ module CreatesCommit
private
def update_flash_notice(success_notice)
- flash[:notice] = success_notice || "Your changes have been successfully committed."
+ flash[:notice] = success_notice || _("Your changes have been successfully committed.")
if create_merge_request?
- if merge_request_exists?
- flash[:notice] = nil
- else
- target = different_project? ? "project" : "branch"
- flash[:notice] = flash[:notice] + " You can now submit a merge request to get this change into the original #{target}."
- end
+ flash[:notice] =
+ if merge_request_exists?
+ nil
+ else
+ mr_message =
+ if different_project?
+ _("You can now submit a merge request to get this change into the original project.")
+ else
+ _("You can now submit a merge request to get this change into the original branch.")
+ end
+
+ flash[:notice] += " " + mr_message
+ end
end
end
diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb
index 57e444319e0..f7137a04437 100644
--- a/app/controllers/concerns/lfs_request.rb
+++ b/app/controllers/concerns/lfs_request.rb
@@ -26,7 +26,7 @@ module LfsRequest
render(
json: {
- message: 'Git LFS is not enabled on this GitLab server, contact your admin.',
+ message: _('Git LFS is not enabled on this GitLab server, contact your admin.'),
documentation_url: help_url
},
status: :not_implemented
@@ -51,7 +51,7 @@ module LfsRequest
def render_lfs_forbidden
render(
json: {
- message: 'Access forbidden. Check your access level.',
+ message: _('Access forbidden. Check your access level.'),
documentation_url: help_url
},
content_type: CONTENT_TYPE,
@@ -62,7 +62,7 @@ module LfsRequest
def render_lfs_not_found
render(
json: {
- message: 'Not found.',
+ message: _('Not found.'),
documentation_url: help_url
},
content_type: CONTENT_TYPE,
diff --git a/app/controllers/concerns/membership_actions.rb b/app/controllers/concerns/membership_actions.rb
index 6402e01ddc0..0b2756c0c6a 100644
--- a/app/controllers/concerns/membership_actions.rb
+++ b/app/controllers/concerns/membership_actions.rb
@@ -9,7 +9,7 @@ module MembershipActions
result = Members::CreateService.new(current_user, create_params).execute(membershipable)
if result[:status] == :success
- redirect_to members_page_url, notice: 'Users were successfully added.'
+ redirect_to members_page_url, notice: _('Users were successfully added.')
else
redirect_to members_page_url, alert: result[:message]
end
@@ -35,9 +35,16 @@ module MembershipActions
respond_to do |format|
format.html do
- source = source_type == 'group' ? 'group and any subresources' : source_type
+ message =
+ begin
+ case membershipable
+ when Namespace
+ _("User was successfully removed from group and any subresources.")
+ else
+ _("User was successfully removed from project.")
+ end
+ end
- message = "User was successfully removed from #{source}."
redirect_to members_page_url, notice: message
end
@@ -49,7 +56,7 @@ module MembershipActions
membershipable.request_access(current_user)
redirect_to polymorphic_path(membershipable),
- notice: 'Your request for access has been queued for review.'
+ notice: _('Your request for access has been queued for review.')
end
def approve_access_request
@@ -68,9 +75,9 @@ module MembershipActions
notice =
if member.request?
- "Your access request to the #{source_type} has been withdrawn."
+ _("Your access request to the %{source_type} has been withdrawn.") % { source_type: source_type }
else
- "You left the \"#{membershipable.human_name}\" #{source_type}."
+ _("You left the \"%{membershipable_human_name}\" %{source_type}.") % { membershipable_human_name: membershipable.human_name, source_type: source_type }
end
respond_to do |format|
@@ -90,9 +97,9 @@ module MembershipActions
if member.invite?
member.resend_invite
- redirect_to members_page_url, notice: 'The invitation was successfully resent.'
+ redirect_to members_page_url, notice: _('The invitation was successfully resent.')
else
- redirect_to members_page_url, alert: 'The invitation has already been accepted.'
+ redirect_to members_page_url, alert: _('The invitation has already been accepted.')
end
end
@@ -125,6 +132,16 @@ module MembershipActions
end
def source_type
- @source_type ||= membershipable.class.to_s.humanize(capitalize: false)
+ @source_type ||=
+ begin
+ case membershipable
+ when Namespace
+ _("group")
+ when Project
+ _("project")
+ else
+ raise "Unknown membershipable type: #{membershipable}!"
+ end
+ end
end
end
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index c3a1b12af84..a8ffa33f1c7 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -12,9 +12,9 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
- redirect_to spammable_path, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
+ redirect_to spammable_path, notice: _("%{spammable_titlecase} was submitted to Akismet successfully.") % { spammable_titlecase: spammable.spammable_entity_type.titlecase }
else
- redirect_to spammable_path, alert: 'Error with Akismet. Please check the logs for more info.'
+ redirect_to spammable_path, alert: _('Error with Akismet. Please check the logs for more info.')
end
end
@@ -33,7 +33,7 @@ module SpammableActions
ensure_spam_config_loaded!
if params[:recaptcha_verification]
- flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.'
+ flash[:alert] = _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.')
end
respond_to do |format|
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 4ec0e94df9a..59f6d3452a3 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -16,7 +16,7 @@ module UploadsActions
end
else
format.json do
- render json: 'Invalid file.', status: :unprocessable_entity
+ render json: _('Invalid file.'), status: :unprocessable_entity
end
end
end
@@ -57,7 +57,7 @@ module UploadsActions
render json: authorized
rescue SocketError
- render json: "Error uploading file", status: :internal_server_error
+ render json: _("Error uploading file"), status: :internal_server_error
end
private
diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb
index 3fa582cf25b..f173c263474 100644
--- a/app/controllers/dashboard/todos_controller.rb
+++ b/app/controllers/dashboard/todos_controller.rb
@@ -21,7 +21,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
format.html do
redirect_to dashboard_todos_path,
status: 302,
- notice: 'Todo was successfully marked as done.'
+ notice: _('Todo was successfully marked as done.')
end
format.js { head :ok }
format.json { render json: todos_counts }
@@ -32,7 +32,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
updated_ids = TodoService.new.mark_todos_as_done(@todos, current_user)
respond_to do |format|
- format.html { redirect_to dashboard_todos_path, status: 302, notice: 'All todos were marked as done.' }
+ format.html { redirect_to dashboard_todos_path, status: 302, notice: _('All todos were marked as done.') }
format.js { head :ok }
format.json { render json: todos_counts.merge(updated_ids: updated_ids) }
end
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index e147d32be2e..7b5dc22815c 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -12,6 +12,7 @@ class GraphqlController < ApplicationController
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) }
def execute
@@ -37,6 +38,10 @@ class GraphqlController < ApplicationController
private
+ def authorize_access_api!
+ access_denied!("API not accessible for user.") unless can?(current_user, :access_api)
+ end
+
# Overridden from the ApplicationController to make the response look like
# a GraphQL response. That is nicely picked up in Graphiql.
def render_404
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index dd8fbf7a029..f8e32451b02 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -16,7 +16,7 @@ class Groups::RunnersController < Groups::ApplicationController
def update
if Ci::UpdateRunnerService.new(@runner).update(runner_params)
- redirect_to group_runner_path(@group, @runner), notice: 'Runner was successfully updated.'
+ redirect_to group_runner_path(@group, @runner), notice: _('Runner was successfully updated.')
else
render 'edit'
end
@@ -30,17 +30,17 @@ class Groups::RunnersController < Groups::ApplicationController
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: 'Runner was successfully updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: _('Runner was successfully updated.')
else
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: 'Runner was not updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: _('Runner was not updated.')
end
end
def pause
if Ci::UpdateRunnerService.new(@runner).update(active: false)
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: 'Runner was successfully updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), notice: _('Runner was successfully updated.')
else
- redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: 'Runner was not updated.'
+ redirect_to group_settings_ci_cd_path(@group, anchor: 'runners-settings'), alert: _('Runner was not updated.')
end
end
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index f378f7ac79a..c465e622de0 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -13,7 +13,7 @@ module Groups
def reset_registration_token
@group.reset_runners_token!
- flash[:notice] = 'New runners registration token has been generated!'
+ flash[:notice] = _('GroupSettings|New runners registration token has been generated!')
redirect_to group_settings_ci_cd_path
end
diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb
index 2b1395f364f..293d76ea765 100644
--- a/app/controllers/import/bitbucket_controller.rb
+++ b/app/controllers/import/bitbucket_controller.rb
@@ -62,7 +62,7 @@ class Import::BitbucketController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
end
diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb
index f333e43b892..643a3bfed1f 100644
--- a/app/controllers/import/bitbucket_server_controller.rb
+++ b/app/controllers/import/bitbucket_server_controller.rb
@@ -25,7 +25,7 @@ class Import::BitbucketServerController < Import::BaseController
repo = bitbucket_client.repo(@project_key, @repo_slug)
unless repo
- return render json: { errors: "Project #{@project_key}/#{@repo_slug} could not be found" }, status: :unprocessable_entity
+ return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity
end
project_name = params[:new_name].presence || repo.name
@@ -41,10 +41,10 @@ class Import::BitbucketServerController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
- rescue BitbucketServer::Connection::ConnectionError => e
- render json: { errors: "Unable to connect to server: #{e}" }, status: :unprocessable_entity
+ rescue BitbucketServer::Connection::ConnectionError => error
+ render json: { errors: _("Unable to connect to server: %{error}") % { error: error } }, status: :unprocessable_entity
end
def configure
@@ -65,8 +65,8 @@ class Import::BitbucketServerController < Import::BaseController
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
- rescue BitbucketServer::Connection::ConnectionError => e
- flash[:alert] = "Unable to connect to server: #{e}"
+ rescue BitbucketServer::Connection::ConnectionError => error
+ flash[:alert] = _("Unable to connect to server: %{error}") % { error: error }
clear_session_data
redirect_to new_import_bitbucket_server_path
end
diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb
index 5a439e6de78..a37ba682b91 100644
--- a/app/controllers/import/fogbugz_controller.rb
+++ b/app/controllers/import/fogbugz_controller.rb
@@ -14,7 +14,7 @@ class Import::FogbugzController < Import::BaseController
res = Gitlab::FogbugzImport::Client.new(import_params.symbolize_keys)
rescue
# If the URI is invalid various errors can occur
- return redirect_to new_import_fogbugz_path, alert: 'Could not connect to FogBugz, check your URL'
+ return redirect_to new_import_fogbugz_path, alert: _('Could not connect to FogBugz, check your URL')
end
session[:fogbugz_token] = res.get_token
session[:fogbugz_uri] = params[:uri]
@@ -29,14 +29,14 @@ class Import::FogbugzController < Import::BaseController
user_map = params[:users]
unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? }
- flash.now[:alert] = 'All users must have a name.'
+ flash.now[:alert] = _('All users must have a name.')
return render 'new_user_map'
end
session[:fogbugz_user_map] = user_map
- flash[:notice] = 'The user map has been saved. Continue by selecting the projects you want to import.'
+ flash[:notice] = _('The user map has been saved. Continue by selecting the projects you want to import.')
redirect_to status_import_fogbugz_path
end
diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb
index 68ad8650dba..a23b2f8139e 100644
--- a/app/controllers/import/gitea_controller.rb
+++ b/app/controllers/import/gitea_controller.rb
@@ -46,7 +46,7 @@ class Import::GiteaController < Import::GithubController
def provider_auth
if session[access_token_key].blank? || provider_url.blank?
redirect_to new_import_gitea_url,
- alert: 'You need to specify both an Access Token and a Host URL.'
+ alert: _('You need to specify both an Access Token and a Host URL.')
end
end
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 498de0b07b8..5ec8e9e6fc5 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -42,7 +42,7 @@ class Import::GitlabController < Import::BaseController
render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end
end
diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb
index 354fba5d204..89889141be6 100644
--- a/app/controllers/import/gitlab_projects_controller.rb
+++ b/app/controllers/import/gitlab_projects_controller.rb
@@ -13,7 +13,7 @@ class Import::GitlabProjectsController < Import::BaseController
def create
unless file_is_valid?
- return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive (ending in .gz)." })
+ return redirect_back_or_default(options: { alert: _("You need to upload a GitLab project export archive (ending in .gz).") })
end
@project = ::Projects::GitlabProjectsImportService.new(current_user, project_params).execute
@@ -21,7 +21,7 @@ class Import::GitlabProjectsController < Import::BaseController
if @project.saved?
redirect_to(
project_path(@project),
- notice: "Project '#{@project.name}' is being imported."
+ notice: _("Project '%{project_name}' is being imported.") % { project_name: @project.name }
)
else
redirect_back_or_default(options: { alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}" })
diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb
index 331f06c3dd6..4dddfbcd20d 100644
--- a/app/controllers/import/google_code_controller.rb
+++ b/app/controllers/import/google_code_controller.rb
@@ -11,18 +11,18 @@ class Import::GoogleCodeController < Import::BaseController
dump_file = params[:dump_file]
unless dump_file.respond_to?(:read)
- return redirect_back_or_default(options: { alert: "You need to upload a Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("You need to upload a Google Takeout archive.") })
end
begin
dump = JSON.parse(dump_file.read)
rescue
- return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("The uploaded file is not a valid Google Takeout archive.") })
end
client = Gitlab::GoogleCodeImport::Client.new(dump)
unless client.valid?
- return redirect_back_or_default(options: { alert: "The uploaded file is not a valid Google Takeout archive." })
+ return redirect_back_or_default(options: { alert: _("The uploaded file is not a valid Google Takeout archive.") })
end
session[:google_code_dump] = dump
@@ -44,13 +44,13 @@ class Import::GoogleCodeController < Import::BaseController
begin
user_map = JSON.parse(user_map_json)
rescue
- flash.now[:alert] = "The entered user map is not a valid JSON user map."
+ flash.now[:alert] = _("The entered user map is not a valid JSON user map.")
return render "new_user_map"
end
unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) }
- flash.now[:alert] = "The entered user map is not a valid JSON user map."
+ flash.now[:alert] = _("The entered user map is not a valid JSON user map.")
return render "new_user_map"
end
@@ -62,7 +62,7 @@ class Import::GoogleCodeController < Import::BaseController
session[:google_code_user_map] = user_map
- flash[:notice] = "The user map has been saved. Continue by selecting the projects you want to import."
+ flash[:notice] = _("The user map has been saved. Continue by selecting the projects you want to import.")
redirect_to status_import_google_code_path
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 0a33856a8d3..7e072788fc9 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -29,7 +29,7 @@ class Projects::BlobController < Projects::ApplicationController
end
def create
- create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
+ create_commit(Files::CreateService, success_notice: _("The file has been successfully created."),
success_path: -> { project_blob_path(@project, File.join(@branch_name, @file_path)) },
failure_view: :new,
failure_path: project_new_blob_path(@project, @ref))
@@ -81,7 +81,7 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
- create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
+ create_commit(Files::DeleteService, success_notice: _("The file has been successfully deleted."),
success_path: -> { after_delete_path },
failure_view: :show,
failure_path: project_blob_path(@project, @id))
@@ -172,8 +172,7 @@ class Projects::BlobController < Projects::ApplicationController
end
if params[:file].present?
- params[:content] = Base64.encode64(params[:file].read)
- params[:encoding] = 'base64'
+ params[:content] = params[:file]
end
@commit_params = {
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 32b7f3207ef..6ff2e222489 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -115,7 +115,7 @@ class Projects::BranchesController < Projects::ApplicationController
DeleteMergedBranchesService.new(@project, current_user).async_execute
redirect_to project_branches_path(@project),
- notice: 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
+ notice: _('Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.')
end
private
@@ -143,7 +143,7 @@ class Projects::BranchesController < Projects::ApplicationController
def redirect_for_legacy_index_sort_or_search
# Normalize a legacy URL with redirect
if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence }
- redirect_to project_branches_filtered_path(@project, state: 'all'), notice: 'Update your bookmarked URLs as filtered/sorted branches URL has been changed.'
+ redirect_to project_branches_filtered_path(@project, state: 'all'), notice: _('Update your bookmarked URLs as filtered/sorted branches URL has been changed.')
end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 6824a07dc76..514b03e23b5 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -38,7 +38,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def update
if deploy_key.update(update_params)
- flash[:notice] = 'Deploy key was successfully updated.'
+ flash[:notice] = _('Deploy key was successfully updated.')
redirect_to_repository_settings(@project, anchor: 'js-deploy-keys-settings')
else
render 'edit'
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index d439db97252..55d5fce9214 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -78,7 +78,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end
def parse_repo_path
- @project, @wiki, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
+ @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse("#{params[:namespace_id]}/#{params[:project_id]}")
end
def render_missing_personal_access_token
@@ -89,13 +89,19 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end
def repository
- wiki? ? project.wiki.repository : project.repository
+ repo_type.repository_for(project)
end
def wiki?
- parse_repo_path unless defined?(@wiki)
+ repo_type.wiki?
+ end
- @wiki
+ def repo_type
+ parse_repo_path unless defined?(@repo_type)
+ # When there a project did not exist, the parsed repo_type would be empty.
+ # In that case, we want to continue with a regular project repository. As we
+ # could create the project if the user pushing is allowed to do so.
+ @repo_type || Gitlab::GlRepository::PROJECT
end
def handle_basic_authentication(login, password)
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index f28af42d1b7..e519cc1f158 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -55,7 +55,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
def render_ok
set_workhorse_internal_api_content_type
- render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name)
+ render json: Gitlab::Workhorse.git_http_ok(repository, repo_type, user, action_name)
end
def render_403(exception)
@@ -99,7 +99,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end
def access_klass
- @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
+ @access_klass ||= repo_type.access_checker_class
end
def project_path
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index bc942ba9288..dc65f9959db 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -18,7 +18,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
flash[:alert] = result[:message] if result[:http_status] == 409
else
- flash[:alert] = 'Please select a group.'
+ flash[:alert] = _('Please select a group.')
end
redirect_to project_project_members_path(project)
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index bc84418b79f..5fa0339f44d 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -32,7 +32,7 @@ class Projects::HooksController < Projects::ApplicationController
def update
if hook.update(hook_params)
- flash[:notice] = 'Hook was successfully updated.'
+ flash[:notice] = _('Hook was successfully updated.')
redirect_to project_settings_integrations_path(@project)
else
render 'edit'
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
index 8b33fa85c1e..8ee0bd26daf 100644
--- a/app/controllers/projects/imports_controller.rb
+++ b/app/controllers/projects/imports_controller.rb
@@ -42,9 +42,9 @@ class Projects::ImportsController < Projects::ApplicationController
def finished_notice
if @project.forked?
- 'The project was successfully forked.'
+ _('The project was successfully forked.')
else
- 'The project was successfully imported.'
+ _('The project was successfully imported.')
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 2cb40697b5c..3d16a368f23 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -96,9 +96,9 @@ class Projects::IssuesController < Projects::ApplicationController
if service.discussions_to_resolve.count(&:resolved?) > 0
flash[:notice] = if service.discussion_to_resolve_id
- "Resolved 1 discussion."
+ _("Resolved 1 discussion.")
else
- "Resolved all discussions."
+ _("Resolved all discussions.")
end
end
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index d5ce790e2d9..35cc32d3e63 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -122,7 +122,7 @@ class Projects::JobsController < Projects::ApplicationController
def erase
if @build.erase(erased_by: current_user)
redirect_to project_job_path(project, @build),
- notice: "Job has been successfully erased!"
+ notice: _("Job has been successfully erased!")
else
respond_422
end
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 640038818f2..386a1f00bd2 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -132,7 +132,7 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to(project_labels_path(@project),
- notice: 'Failed to promote label due to internal error. Please contact administrators.')
+ notice: _('Failed to promote label due to internal error. Please contact administrators.'))
end
format.js
end
diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb
index be40077d389..42c415757f9 100644
--- a/app/controllers/projects/lfs_api_controller.rb
+++ b/app/controllers/projects/lfs_api_controller.rb
@@ -26,7 +26,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
def deprecated
render(
json: {
- message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.',
+ message: _('Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'),
documentation_url: "#{Gitlab.config.gitlab.url}/help"
},
status: :not_implemented
@@ -62,7 +62,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController
else
object[:error] = {
code: 404,
- message: "Object does not exist on the server or you don't have permissions to access it"
+ message: _("Object does not exist on the server or you don't have permissions to access it")
}
end
end
diff --git a/app/controllers/projects/merge_requests/conflicts_controller.rb b/app/controllers/projects/merge_requests/conflicts_controller.rb
index 045a4e974fe..011ac9a42f8 100644
--- a/app/controllers/projects/merge_requests/conflicts_controller.rb
+++ b/app/controllers/projects/merge_requests/conflicts_controller.rb
@@ -16,12 +16,12 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
render json: @conflicts_list
elsif @merge_request.can_be_merged?
render json: {
- message: 'The merge conflicts for this merge request have already been resolved. Please return to the merge request.',
+ message: _('The merge conflicts for this merge request have already been resolved. Please return to the merge request.'),
type: 'error'
}
else
render json: {
- message: 'The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.',
+ message: _('The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally.'),
type: 'error'
}
end
@@ -43,7 +43,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
return render_404 unless @conflicts_list.can_be_resolved_in_ui?
if @merge_request.can_be_merged?
- render status: :bad_request, json: { message: 'The merge conflicts for this merge request have already been resolved.' }
+ render status: :bad_request, json: { message: _('The merge conflicts for this merge request have already been resolved.') }
return
end
@@ -52,7 +52,7 @@ class Projects::MergeRequests::ConflictsController < Projects::MergeRequests::Ap
.new(merge_request)
.execute(current_user, params)
- flash[:notice] = 'All merge conflicts were resolved. The merge request can now be merged.'
+ flash[:notice] = _('All merge conflicts were resolved. The merge request can now be merged.')
render json: { redirect_to: project_merge_request_url(@project, @merge_request, resolved_conflicts: true) }
rescue Gitlab::Git::Conflict::Resolver::ResolutionError => e
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2b78abc66df..34cb0416965 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -16,10 +16,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
- before_action only: [:show] do
- push_frontend_feature_flag(:expand_diff_full_file)
- end
-
def index
@merge_requests = @issuables
@@ -319,7 +315,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def serializer
- MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
+ ::Gitlab::GitalyClient.allow_ref_name_caching do
+ MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
+ end
end
def define_edit_vars
diff --git a/app/controllers/projects/mirrors_controller.rb b/app/controllers/projects/mirrors_controller.rb
index ab7ab13657a..ef330ae00f4 100644
--- a/app/controllers/projects/mirrors_controller.rb
+++ b/app/controllers/projects/mirrors_controller.rb
@@ -18,7 +18,7 @@ class Projects::MirrorsController < Projects::ApplicationController
result = ::Projects::UpdateService.new(project, current_user, mirror_params).execute
if result[:status] == :success
- flash[:notice] = 'Mirroring settings were successfully updated.'
+ flash[:notice] = _('Mirroring settings were successfully updated.')
else
flash[:alert] = project.errors.full_messages.join(', ').html_safe
end
@@ -38,7 +38,7 @@ class Projects::MirrorsController < Projects::ApplicationController
def update_now
if params[:sync_remote]
project.update_remote_mirrors
- flash[:notice] = "The remote repository is being updated..."
+ flash[:notice] = _("The remote repository is being updated...")
end
redirect_to_repository_settings(project, anchor: 'js-push-remote-settings')
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index acf56f0eb6a..fd5b89298a7 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -50,9 +50,11 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
job_id = RunPipelineScheduleWorker.perform_async(schedule.id, current_user.id)
if job_id
- flash[:notice] = "Successfully scheduled a pipeline to run. Go to the <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details.".html_safe
+ link_to_pipelines = view_context.link_to(_('Pipelines page'), project_pipelines_path(@project))
+ message = _("Successfully scheduled a pipeline to run. Go to the %{link_to_pipelines} for details.").html_safe % { link_to_pipelines: link_to_pipelines }
+ flash[:notice] = message.html_safe
else
- flash[:alert] = 'Unable to schedule a pipeline to run immediately'
+ flash[:alert] = _('Unable to schedule a pipeline to run immediately')
end
redirect_to pipeline_schedules_path(@project)
@@ -85,7 +87,7 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
return unless limiter.throttled?([current_user, schedule], 1)
- flash[:alert] = 'You cannot play this scheduled pipeline at the moment. Please wait a minute.'
+ flash[:alert] = _('You cannot play this scheduled pipeline at the moment. Please wait a minute.')
redirect_to pipeline_schedules_path(@project)
end
diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
index 91f40b90aa8..ca62f54813b 100644
--- a/app/controllers/projects/runners_controller.rb
+++ b/app/controllers/projects/runners_controller.rb
@@ -15,7 +15,7 @@ class Projects::RunnersController < Projects::ApplicationController
def update
if Ci::UpdateRunnerService.new(@runner).update(runner_params)
- redirect_to project_runner_path(@project, @runner), notice: 'Runner was successfully updated.'
+ redirect_to project_runner_path(@project, @runner), notice: _('Runner was successfully updated.')
else
render 'edit'
end
@@ -31,17 +31,17 @@ class Projects::RunnersController < Projects::ApplicationController
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
- redirect_to project_runners_path(@project), notice: 'Runner was successfully updated.'
+ redirect_to project_runners_path(@project), notice: _('Runner was successfully updated.')
else
- redirect_to project_runners_path(@project), alert: 'Runner was not updated.'
+ redirect_to project_runners_path(@project), alert: _('Runner was not updated.')
end
end
def pause
if Ci::UpdateRunnerService.new(@runner).update(active: false)
- redirect_to project_runners_path(@project), notice: 'Runner was successfully updated.'
+ redirect_to project_runners_path(@project), notice: _('Runner was successfully updated.')
else
- redirect_to project_runners_path(@project), alert: 'Runner was not updated.'
+ redirect_to project_runners_path(@project), alert: _('Runner was not updated.')
end
end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index f1c9d0d0f77..e0df51590ae 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -43,20 +43,20 @@ class Projects::ServicesController < Projects::ApplicationController
if outcome[:success]
{}
else
- { error: true, message: 'Test failed.', service_response: outcome[:result].to_s, test_failed: true }
+ { error: true, message: _('Test failed.'), service_response: outcome[:result].to_s, test_failed: true }
end
else
- { error: true, message: 'Validations failed.', service_response: @service.errors.full_messages.join(','), test_failed: false }
+ { error: true, message: _('Validations failed.'), service_response: @service.errors.full_messages.join(','), test_failed: false }
end
rescue Gitlab::HTTP::BlockedUrlError => e
- { error: true, message: 'Test failed.', service_response: e.message, test_failed: true }
+ { error: true, message: _('Test failed.'), service_response: e.message, test_failed: true }
end
def success_message
if @service.active?
- "#{@service.title} activated."
+ _("%{service_title} activated.") % { service_title: @service.title }
else
- "#{@service.title} settings saved, but not activated."
+ _("%{service_title} settings saved, but not activated.") % { service_title: @service.title }
end
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index f2f63e986bb..d1c5cef76fa 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -13,7 +13,7 @@ module Projects
Projects::UpdateService.new(project, current_user, update_params).tap do |service|
result = service.execute
if result[:status] == :success
- flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated."
+ flash[:notice] = _("Pipelines settings for '%{project_name}' were successfully updated.") % { project_name: @project.name }
run_autodevops_pipeline(service)
@@ -39,7 +39,7 @@ module Projects
def reset_registration_token
@project.reset_runners_token!
- flash[:notice] = 'New runners registration token has been generated!'
+ flash[:notice] = _('New runners registration token has been generated!')
redirect_to namespace_project_settings_ci_cd_path
end
@@ -58,7 +58,7 @@ module Projects
return unless service.run_auto_devops_pipeline?
if @project.empty_repo?
- flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+ flash[:warning] = _("This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch.")
return
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index edebfc55c17..90d53aa08ea 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -37,7 +37,7 @@ class Projects::TreeController < Projects::ApplicationController
def create_dir
return render_404 unless @commit_params.values.all?
- create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
+ create_commit(Files::CreateDirService, success_notice: _("The directory has been successfully created."),
success_path: project_tree_path(@project, File.join(@branch_name, @dir_name)),
failure_path: project_tree_path(@project, @ref))
end
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index c7b4ebb2b24..284e119ca06 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -16,9 +16,9 @@ class Projects::TriggersController < Projects::ApplicationController
@trigger = project.triggers.create(trigger_params.merge(owner: current_user))
if @trigger.valid?
- flash[:notice] = 'Trigger was created successfully.'
+ flash[:notice] = _('Trigger was created successfully.')
else
- flash[:alert] = 'You could not create a new trigger.'
+ flash[:alert] = _('You could not create a new trigger.')
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
@@ -26,9 +26,9 @@ class Projects::TriggersController < Projects::ApplicationController
def take_ownership
if trigger.update(owner: current_user)
- flash[:notice] = 'Trigger was re-assigned.'
+ flash[:notice] = _('Trigger was re-assigned.')
else
- flash[:alert] = 'You could not take ownership of trigger.'
+ flash[:alert] = _('You could not take ownership of trigger.')
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
@@ -39,7 +39,7 @@ class Projects::TriggersController < Projects::ApplicationController
def update
if trigger.update(trigger_params)
- redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: 'Trigger was successfully updated.'
+ redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), notice: _('Trigger was successfully updated.')
else
render action: "edit"
end
@@ -47,9 +47,9 @@ class Projects::TriggersController < Projects::ApplicationController
def destroy
if trigger.destroy
- flash[:notice] = "Trigger removed."
+ flash[:notice] = _("Trigger removed.")
else
- flash[:alert] = "Could not remove the trigger."
+ flash[:alert] = _("Could not remove the trigger.")
end
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers'), status: :found
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 88dd111132b..da2420633ef 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -49,7 +49,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.valid?
redirect_to(
project_wiki_path(@project, @page),
- notice: 'Wiki was successfully updated.'
+ notice: _('Wiki was successfully updated.')
)
else
render 'edit'
@@ -65,7 +65,7 @@ class Projects::WikisController < Projects::ApplicationController
if @page.persisted?
redirect_to(
project_wiki_path(@project, @page),
- notice: 'Wiki was successfully updated.'
+ notice: _('Wiki was successfully updated.')
)
else
render action: "edit"
@@ -85,7 +85,7 @@ class Projects::WikisController < Projects::ApplicationController
else
redirect_to(
project_wiki_path(@project, :home),
- notice: "Page not found"
+ notice: _("Page not found")
)
end
end
@@ -95,7 +95,7 @@ class Projects::WikisController < Projects::ApplicationController
redirect_to project_wiki_path(@project, :home),
status: 302,
- notice: "Page was successfully deleted"
+ notice: _("Page was successfully deleted")
rescue Gitlab::Git::Wiki::OperationError => e
@error = e
render 'edit'
@@ -118,7 +118,7 @@ class Projects::WikisController < Projects::ApplicationController
@sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15))
end
rescue ProjectWiki::CouldNotCreateWikiError
- flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
+ flash[:notice] = _("Could not create Wiki Repository at this time. Please try again later.")
redirect_to project_path(@project)
false
end
@@ -155,7 +155,7 @@ class Projects::WikisController < Projects::ApplicationController
end
def set_encoding_error
- flash.now[:notice] = "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+ flash.now[:notice] = _("The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.")
end
def file_blob
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 90d4bc674d9..a80ab3bcd28 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -14,8 +14,6 @@ class SearchController < ApplicationController
layout 'search'
def show
- search_service = SearchService.new(current_user, params)
-
@project = search_service.project
@group = search_service.group
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 0080123407d..7d419103b1c 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -11,6 +11,7 @@
# parent: Group
# all_available: boolean (defaults to true)
# min_access_level: integer
+# exclude_group_ids: array of integers
#
# Users with full private access can see all groups. The `owned` and `parent`
# params can be used to restrict the groups that are returned.
@@ -29,6 +30,7 @@ class GroupsFinder < UnionFinder
items = all_groups.map do |item|
item = by_parent(item)
item = by_custom_attributes(item)
+ item = exclude_group_ids(item)
item
end
@@ -72,6 +74,12 @@ class GroupsFinder < UnionFinder
end
# rubocop: enable CodeReuse/ActiveRecord
+ def exclude_group_ids(groups)
+ return groups unless params[:exclude_group_ids]
+
+ groups.id_not_in(params[:exclude_group_ids])
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def by_parent(groups)
return groups unless params[:parent]
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 93d3c991846..0319e95d439 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -81,7 +81,7 @@ class ProjectsFinder < UnionFinder
if private_only?
current_user.authorized_projects
else
- Project.public_or_visible_to_user(current_user)
+ Project.public_or_visible_to_user(current_user, params[:visibility_level])
end
end
end
diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb
new file mode 100644
index 00000000000..2987354b556
--- /dev/null
+++ b/app/graphql/types/ci/detailed_status_type.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+module Types
+ module Ci
+ class DetailedStatusType < BaseObject
+ graphql_name 'DetailedStatus'
+
+ field :group, GraphQL::STRING_TYPE, null: false
+ field :icon, GraphQL::STRING_TYPE, null: false
+ field :favicon, GraphQL::STRING_TYPE, null: false
+ field :details_path, GraphQL::STRING_TYPE, null: false
+ field :has_details, GraphQL::BOOLEAN_TYPE, null: false, method: :has_details?
+ field :label, GraphQL::STRING_TYPE, null: false
+ field :text, GraphQL::STRING_TYPE, null: false
+ field :tooltip, GraphQL::STRING_TYPE, null: false, method: :status_tooltip
+ end
+ end
+end
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index 2bbffad4563..18696293b97 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -13,6 +13,10 @@ module Types
field :sha, GraphQL::STRING_TYPE, null: false
field :before_sha, GraphQL::STRING_TYPE, null: true
field :status, PipelineStatusEnum, null: false
+ field :detailed_status,
+ Types::Ci::DetailedStatusType,
+ null: false,
+ resolve: -> (obj, _args, ctx) { obj.detailed_status(ctx[:current_user]) }
field :duration,
GraphQL::INT_TYPE,
null: true,
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 9d028dccad7..7af766c8544 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -118,11 +118,12 @@ module GroupsHelper
end
def parent_group_options(current_group)
- groups = current_user.owned_groups.sort_by(&:human_name).map do |group|
+ exclude_groups = current_group.self_and_descendants.pluck_primary_key
+ exclude_groups << current_group.parent_id if current_group.parent_id
+ groups = GroupsFinder.new(current_user, min_access_level: Gitlab::Access::OWNER, exclude_group_ids: exclude_groups).execute.sort_by(&:human_name).map do |group|
{ id: group.id, text: group.human_name }
end
- groups.delete_if { |group| group[:id] == current_group.id }
groups.to_json
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 8110377850b..69520e33774 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -33,10 +33,15 @@ module SearchHelper
"Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
end
- def find_project_for_result_blob(result)
+ def find_project_for_result_blob(projects, result)
@project
end
+ # Used in EE
+ def blob_projects(results)
+ nil
+ end
+
def parse_search_result(result)
result
end
@@ -45,6 +50,10 @@ module SearchHelper
filename
end
+ def search_service
+ @search_service ||= ::SearchService.new(current_user, params)
+ end
+
private
# Autocomplete results for various settings pages
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 1b78fd04ebb..a3a1748142f 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AbuseReport < ActiveRecord::Base
+class AbuseReport < ApplicationRecord
include CacheMarkdownField
cache_markdown_field :message, pipeline: :single_line
diff --git a/app/models/appearance.rb b/app/models/appearance.rb
index bdee9b2b73c..2815a117f7f 100644
--- a/app/models/appearance.rb
+++ b/app/models/appearance.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Appearance < ActiveRecord::Base
+class Appearance < ApplicationRecord
include CacheableAttributes
include CacheMarkdownField
include ObjectStorage::BackgroundMove
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index a3d662d8250..6976185264e 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -7,6 +7,14 @@ class ApplicationRecord < ActiveRecord::Base
where(id: ids)
end
+ def self.id_not_in(ids)
+ where.not(id: ids)
+ end
+
+ def self.pluck_primary_key
+ where(nil).pluck(self.primary_key)
+ end
+
def self.safe_find_or_create_by!(*args)
safe_find_or_create_by(*args).tap do |record|
record.validate! unless record.persisted?
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 9cc7c0a1b97..9e91e4ab4b9 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ApplicationSetting < ActiveRecord::Base
+class ApplicationSetting < ApplicationRecord
include CacheableAttributes
include CacheMarkdownField
include TokenAuthenticatable
diff --git a/app/models/application_setting/term.rb b/app/models/application_setting/term.rb
index 498701ba22b..723540c9b91 100644
--- a/app/models/application_setting/term.rb
+++ b/app/models/application_setting/term.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class ApplicationSetting
- class Term < ActiveRecord::Base
+ class Term < ApplicationRecord
include CacheMarkdownField
has_many :term_agreements
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 8508c88d406..6ef2914ac11 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AuditEvent < ActiveRecord::Base
+class AuditEvent < ApplicationRecord
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :user, foreign_key: :author_id
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index ddc516ccb60..e26162f6151 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class AwardEmoji < ActiveRecord::Base
+class AwardEmoji < ApplicationRecord
DOWNVOTE_NAME = "thumbsdown".freeze
UPVOTE_NAME = "thumbsup".freeze
diff --git a/app/models/badge.rb b/app/models/badge.rb
index f016654206b..a244ed473de 100644
--- a/app/models/badge.rb
+++ b/app/models/badge.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Badge < ActiveRecord::Base
+class Badge < ApplicationRecord
include FromUnion
# This structure sets the placeholders that the urls
diff --git a/app/models/board.rb b/app/models/board.rb
index 758a71d6903..e08db764f65 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Board < ActiveRecord::Base
+class Board < ApplicationRecord
belongs_to :group
belongs_to :project
diff --git a/app/models/board_group_recent_visit.rb b/app/models/board_group_recent_visit.rb
index f5b75270595..2f1cd830791 100644
--- a/app/models/board_group_recent_visit.rb
+++ b/app/models/board_group_recent_visit.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Tracks which boards in a specific group a user has visited
-class BoardGroupRecentVisit < ActiveRecord::Base
+class BoardGroupRecentVisit < ApplicationRecord
belongs_to :user
belongs_to :group
belongs_to :board
diff --git a/app/models/board_project_recent_visit.rb b/app/models/board_project_recent_visit.rb
index 2a1b14b3ae0..236d88e909c 100644
--- a/app/models/board_project_recent_visit.rb
+++ b/app/models/board_project_recent_visit.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Tracks which boards in a specific project a user has visited
-class BoardProjectRecentVisit < ActiveRecord::Base
+class BoardProjectRecentVisit < ApplicationRecord
belongs_to :user
belongs_to :project
belongs_to :board
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 1c95abdd9ee..18fe2a9624f 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class BroadcastMessage < ActiveRecord::Base
+class BroadcastMessage < ApplicationRecord
include CacheMarkdownField
include Sortable
diff --git a/app/models/chat_name.rb b/app/models/chat_name.rb
index 03b0af53046..0041595baba 100644
--- a/app/models/chat_name.rb
+++ b/app/models/chat_name.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ChatName < ActiveRecord::Base
+class ChatName < ApplicationRecord
LAST_USED_AT_INTERVAL = 1.hour
belongs_to :service
diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb
index 4e724f9adf7..52b5a7b4a91 100644
--- a/app/models/chat_team.rb
+++ b/app/models/chat_team.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ChatTeam < ActiveRecord::Base
+class ChatTeam < ApplicationRecord
validates :team_id, presence: true
validates :namespace, uniqueness: true
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 59f47effff7..1bd517641ac 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -26,7 +26,8 @@ module Ci
belongs_to :erased_by, class_name: 'User'
RUNNER_FEATURES = {
- upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? }
+ upload_multiple_artifacts: -> (build) { build.publishes_artifacts_reports? },
+ refspecs: -> (build) { build.merge_request_ref? }
}.freeze
has_one :deployment, as: :deployable, class_name: 'Deployment'
@@ -47,7 +48,8 @@ module Ci
delegate :terminal_specification, to: :runner_session, allow_nil: true
delegate :gitlab_deploy_token, to: :project
delegate :trigger_short_token, to: :trigger_request, allow_nil: true
- delegate :merge_request_event?, to: :pipeline
+ delegate :merge_request_event?, :merge_request_ref?,
+ :legacy_detached_merge_request_pipeline?, to: :pipeline
##
# Since Gitlab 11.5, deployments records started being created right after
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index cd8eb774cf5..f281cbd1d6f 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -3,7 +3,7 @@
module Ci
# The purpose of this class is to store Build related data that can be disposed.
# Data that should be persisted forever, should be stored with Ci::Build model.
- class BuildMetadata < ActiveRecord::Base
+ class BuildMetadata < ApplicationRecord
extend Gitlab::Ci::Model
include Presentable
include ChronicDurationAttribute
diff --git a/app/models/ci/build_runner_session.rb b/app/models/ci/build_runner_session.rb
index 457d7eeab6a..061eff090f5 100644
--- a/app/models/ci/build_runner_session.rb
+++ b/app/models/ci/build_runner_session.rb
@@ -3,7 +3,7 @@
module Ci
# The purpose of this class is to store Build related runner session.
# Data will be removed after transitioning from running to any state.
- class BuildRunnerSession < ActiveRecord::Base
+ class BuildRunnerSession < ApplicationRecord
extend Gitlab::Ci::Model
self.table_name = 'ci_builds_runner_session'
diff --git a/app/models/ci/build_trace_chunk.rb b/app/models/ci/build_trace_chunk.rb
index 75017f224a0..0a7a0e0772b 100644
--- a/app/models/ci/build_trace_chunk.rb
+++ b/app/models/ci/build_trace_chunk.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class BuildTraceChunk < ActiveRecord::Base
+ class BuildTraceChunk < ApplicationRecord
include FastDestroyAll
include ::Gitlab::ExclusiveLeaseHelpers
extend Gitlab::Ci::Model
diff --git a/app/models/ci/build_trace_section.rb b/app/models/ci/build_trace_section.rb
index a4bee59c83b..8be42eb48d6 100644
--- a/app/models/ci/build_trace_section.rb
+++ b/app/models/ci/build_trace_section.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class BuildTraceSection < ActiveRecord::Base
+ class BuildTraceSection < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :build, class_name: 'Ci::Build'
diff --git a/app/models/ci/build_trace_section_name.rb b/app/models/ci/build_trace_section_name.rb
index cbdf3c4b673..c065cfea14e 100644
--- a/app/models/ci/build_trace_section_name.rb
+++ b/app/models/ci/build_trace_section_name.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class BuildTraceSectionName < ActiveRecord::Base
+ class BuildTraceSectionName < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :project
diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb
index 323ff560564..0e50265c7ba 100644
--- a/app/models/ci/group_variable.rb
+++ b/app/models/ci/group_variable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class GroupVariable < ActiveRecord::Base
+ class GroupVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
include Presentable
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index 789bb293811..99512a7c1dd 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class JobArtifact < ActiveRecord::Base
+ class JobArtifact < ApplicationRecord
include AfterCommitQueue
include ObjectStorage::BackgroundMove
extend Gitlab::Ci::Model
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 826b3f82bbf..abac16a138d 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Pipeline < ActiveRecord::Base
+ class Pipeline < ApplicationRecord
extend Gitlab::Ci::Model
include HasStatus
include Importable
@@ -184,7 +184,7 @@ module Ci
scope :sort_by_merge_request_pipelines, -> do
sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC'
- query = ActiveRecord::Base.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
+ query = ApplicationRecord.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend
order(query)
end
@@ -465,9 +465,9 @@ module Ci
end
def latest?
- return false unless ref && commit.present?
+ return false unless git_ref && commit.present?
- project.commit(ref) == commit
+ project.commit(git_ref) == commit
end
def retried
@@ -738,6 +738,10 @@ module Ci
triggered_by_merge_request? && target_sha.nil?
end
+ def legacy_detached_merge_request_pipeline?
+ detached_merge_request_pipeline? && !merge_request_ref?
+ end
+
def merge_request_pipeline?
triggered_by_merge_request? && target_sha.present?
end
@@ -746,6 +750,10 @@ module Ci
triggered_by_merge_request? && target_sha == merge_request.target_branch_sha
end
+ def merge_request_ref?
+ MergeRequest.merge_request_ref?(ref)
+ end
+
def matches_sha_or_source_sha?(sha)
self.sha == sha || self.source_sha == sha
end
@@ -781,16 +789,18 @@ module Ci
end
def git_ref
- if merge_request_event?
- ##
- # In the future, we're going to change this ref to
- # merge request's merged reference, such as "refs/merge-requests/:iid/merge".
- # In order to do that, we have to update GitLab-Runner's source pulling
- # logic.
- # See https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1092
- Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
- else
- super
+ strong_memoize(:git_ref) do
+ if merge_request_event?
+ ##
+ # In the future, we're going to change this ref to
+ # merge request's merged reference, such as "refs/merge-requests/:iid/merge".
+ # In order to do that, we have to update GitLab-Runner's source pulling
+ # logic.
+ # See https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1092
+ Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
+ else
+ super
+ end
end
end
diff --git a/app/models/ci/pipeline_chat_data.rb b/app/models/ci/pipeline_chat_data.rb
index 8d37500fec5..65466a8c6f8 100644
--- a/app/models/ci/pipeline_chat_data.rb
+++ b/app/models/ci/pipeline_chat_data.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class PipelineChatData < ActiveRecord::Base
+ class PipelineChatData < ApplicationRecord
self.table_name = 'ci_pipeline_chat_data'
belongs_to :chat_name
diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index 1c1f203bdb2..1454b2dfb39 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class PipelineSchedule < ActiveRecord::Base
+ class PipelineSchedule < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
include IgnorableColumn
diff --git a/app/models/ci/pipeline_schedule_variable.rb b/app/models/ci/pipeline_schedule_variable.rb
index fbb9987cab2..be6e5e76c31 100644
--- a/app/models/ci/pipeline_schedule_variable.rb
+++ b/app/models/ci/pipeline_schedule_variable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class PipelineScheduleVariable < ActiveRecord::Base
+ class PipelineScheduleVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
diff --git a/app/models/ci/pipeline_variable.rb b/app/models/ci/pipeline_variable.rb
index 08514d6af4e..51a6272e1ff 100644
--- a/app/models/ci/pipeline_variable.rb
+++ b/app/models/ci/pipeline_variable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class PipelineVariable < ActiveRecord::Base
+ class PipelineVariable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 43f040a91ae..07d00503861 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Runner < ActiveRecord::Base
+ class Runner < ApplicationRecord
extend Gitlab::Ci::Model
include Gitlab::SQL::Pattern
include IgnorableColumn
diff --git a/app/models/ci/runner_namespace.rb b/app/models/ci/runner_namespace.rb
index 22b80b98551..6903e8a21a1 100644
--- a/app/models/ci/runner_namespace.rb
+++ b/app/models/ci/runner_namespace.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class RunnerNamespace < ActiveRecord::Base
+ class RunnerNamespace < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :runner, inverse_of: :runner_namespaces, validate: true
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 1a718d24141..f5bd50dc5a3 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class RunnerProject < ActiveRecord::Base
+ class RunnerProject < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :runner, inverse_of: :runner_projects
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index 098f5189517..b25b0369666 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Stage < ActiveRecord::Base
+ class Stage < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
include HasStatus
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 637148c4ce4..8927bb9bc18 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Trigger < ActiveRecord::Base
+ class Trigger < ApplicationRecord
extend Gitlab::Ci::Model
include IgnorableColumn
include Presentable
diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb
index 0b52c690e93..5daf3dd192d 100644
--- a/app/models/ci/trigger_request.rb
+++ b/app/models/ci/trigger_request.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class TriggerRequest < ActiveRecord::Base
+ class TriggerRequest < ApplicationRecord
extend Gitlab::Ci::Model
belongs_to :trigger
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 64836ea4fa4..a77bbef0fca 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Ci
- class Variable < ActiveRecord::Base
+ class Variable < ApplicationRecord
extend Gitlab::Ci::Model
include HasVariable
include Presentable
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index c758577815a..ac0e7eb03bc 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -2,7 +2,7 @@
module Clusters
module Applications
- class CertManager < ActiveRecord::Base
+ class CertManager < ApplicationRecord
VERSION = 'v0.5.2'.freeze
self.table_name = 'clusters_applications_cert_managers'
diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb
index 423071ec024..71aff00077d 100644
--- a/app/models/clusters/applications/helm.rb
+++ b/app/models/clusters/applications/helm.rb
@@ -4,7 +4,7 @@ require 'openssl'
module Clusters
module Applications
- class Helm < ActiveRecord::Base
+ class Helm < ApplicationRecord
self.table_name = 'clusters_applications_helm'
attr_encrypted :ca_key,
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 567f1a2267f..376d54aab2c 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -2,7 +2,7 @@
module Clusters
module Applications
- class Ingress < ActiveRecord::Base
+ class Ingress < ApplicationRecord
VERSION = '1.1.2'.freeze
self.table_name = 'clusters_applications_ingress'
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 7efcc175f9f..f86ff3551a1 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -2,7 +2,7 @@
module Clusters
module Applications
- class Jupyter < ActiveRecord::Base
+ class Jupyter < ApplicationRecord
VERSION = '0.9-174bbd5'.freeze
self.table_name = 'clusters_applications_jupyter'
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 347c3c8c37f..f7e54833296 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -2,8 +2,8 @@
module Clusters
module Applications
- class Knative < ActiveRecord::Base
- VERSION = '0.2.2'.freeze
+ class Knative < ApplicationRecord
+ VERSION = '0.3.0'.freeze
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze
FETCH_IP_ADDRESS_DELAY = 30.seconds
@@ -86,7 +86,7 @@ module Clusters
end
def ingress_service
- cluster.kubeclient.get_service('knative-ingressgateway', 'istio-system')
+ cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system')
end
def services_for(ns: namespace)
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index fa7ce363531..954c29da196 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -2,7 +2,7 @@
module Clusters
module Applications
- class Prometheus < ActiveRecord::Base
+ class Prometheus < ApplicationRecord
include PrometheusAdapter
VERSION = '6.7.3'
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index ef9cc4bd6d6..8cb81bfcbe4 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -2,7 +2,7 @@
module Clusters
module Applications
- class Runner < ActiveRecord::Base
+ class Runner < ApplicationRecord
VERSION = '0.3.0'.freeze
self.table_name = 'clusters_applications_runners'
@@ -13,7 +13,7 @@ module Clusters
include ::Clusters::Concerns::ApplicationData
belongs_to :runner, class_name: 'Ci::Runner', foreign_key: :runner_id
- delegate :project, to: :cluster
+ delegate :project, :group, to: :cluster
default_value_for :version, VERSION
@@ -55,12 +55,17 @@ module Clusters
end
def runner_create_params
- {
+ attributes = {
name: 'kubernetes-cluster',
- runner_type: :project_type,
- tag_list: %w(kubernetes cluster),
- projects: [project]
+ runner_type: cluster.cluster_type,
+ tag_list: %w[kubernetes cluster]
}
+
+ if cluster.group_type?
+ attributes.merge(groups: [group])
+ elsif cluster.project_type?
+ attributes.merge(projects: [project])
+ end
end
def gitlab_url
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 5156c7d7514..4262c03498d 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -1,22 +1,24 @@
# frozen_string_literal: true
module Clusters
- class Cluster < ActiveRecord::Base
+ class Cluster < ApplicationRecord
include Presentable
include Gitlab::Utils::StrongMemoize
include FromUnion
self.table_name = 'clusters'
+ PROJECT_ONLY_APPLICATIONS = {
+ Applications::Jupyter.application_name => Applications::Jupyter,
+ Applications::Knative.application_name => Applications::Knative,
+ Applications::Prometheus.application_name => Applications::Prometheus
+ }.freeze
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
Applications::Ingress.application_name => Applications::Ingress,
Applications::CertManager.application_name => Applications::CertManager,
- Applications::Prometheus.application_name => Applications::Prometheus,
- Applications::Runner.application_name => Applications::Runner,
- Applications::Jupyter.application_name => Applications::Jupyter,
- Applications::Knative.application_name => Applications::Knative
- }.freeze
+ Applications::Runner.application_name => Applications::Runner
+ }.merge(PROJECT_ONLY_APPLICATIONS).freeze
DEFAULT_ENVIRONMENT = '*'.freeze
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze
@@ -70,6 +72,7 @@ module Clusters
delegate :external_hostname, to: :application_ingress, prefix: true, allow_nil: true
alias_attribute :base_domain, :domain
+ alias_attribute :provided_by_user?, :user?
enum cluster_type: {
instance_type: 1,
@@ -149,10 +152,6 @@ module Clusters
return platform_kubernetes if kubernetes?
end
- def managed?
- !user?
- end
-
def all_projects
if project_type?
projects
diff --git a/app/models/clusters/group.rb b/app/models/clusters/group.rb
index 2b08a9e47f0..27f39b53579 100644
--- a/app/models/clusters/group.rb
+++ b/app/models/clusters/group.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Clusters
- class Group < ActiveRecord::Base
+ class Group < ApplicationRecord
self.table_name = 'cluster_groups'
belongs_to :cluster, class_name: 'Clusters::Cluster'
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
index 7fc75e00cd0..b0c4900546e 100644
--- a/app/models/clusters/kubernetes_namespace.rb
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Clusters
- class KubernetesNamespace < ActiveRecord::Base
+ class KubernetesNamespace < ApplicationRecord
include Gitlab::Kubernetes
self.table_name = 'clusters_kubernetes_namespaces'
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 7786b48429c..2ae141190a8 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -2,7 +2,7 @@
module Clusters
module Platforms
- class Kubernetes < ActiveRecord::Base
+ class Kubernetes < ApplicationRecord
include Gitlab::Kubernetes
include ReactiveCaching
include EnumWithNil
@@ -54,7 +54,7 @@ module Clusters
delegate :project, to: :cluster, allow_nil: true
delegate :enabled?, to: :cluster, allow_nil: true
- delegate :managed?, to: :cluster, allow_nil: true
+ delegate :provided_by_user?, to: :cluster, allow_nil: true
delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true
delegate :kubernetes_namespace, to: :cluster
@@ -219,7 +219,7 @@ module Clusters
end
def prevent_modification
- return unless managed?
+ return if provided_by_user?
if api_url_changed? || token_changed? || ca_pem_changed?
errors.add(:base, _('Cannot modify managed Kubernetes cluster'))
diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb
index 15092b1c9d2..d2b68b3f117 100644
--- a/app/models/clusters/project.rb
+++ b/app/models/clusters/project.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Clusters
- class Project < ActiveRecord::Base
+ class Project < ApplicationRecord
self.table_name = 'cluster_projects'
belongs_to :cluster, class_name: 'Clusters::Cluster'
diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb
index 16b59cd9d14..390748bf252 100644
--- a/app/models/clusters/providers/gcp.rb
+++ b/app/models/clusters/providers/gcp.rb
@@ -2,7 +2,7 @@
module Clusters
module Providers
- class Gcp < ActiveRecord::Base
+ class Gcp < ApplicationRecord
self.table_name = 'cluster_providers_gcp'
belongs_to :cluster, inverse_of: :provider_gcp, class_name: 'Clusters::Cluster'
diff --git a/app/models/commit_collection.rb b/app/models/commit_collection.rb
index a9a2e9c81eb..52524456439 100644
--- a/app/models/commit_collection.rb
+++ b/app/models/commit_collection.rb
@@ -28,10 +28,43 @@ class CommitCollection
def without_merge_commits
strong_memoize(:without_merge_commits) do
- commits.reject(&:merge_commit?)
+ # `#enrich!` the collection to ensure all commits contain
+ # the necessary parent data
+ enrich!.commits.reject(&:merge_commit?)
end
end
+ def unenriched
+ commits.reject(&:gitaly_commit?)
+ end
+
+ def fully_enriched?
+ unenriched.empty?
+ end
+
+ # Batch load any commits that are not backed by full gitaly data, and
+ # replace them in the collection.
+ def enrich!
+ # A project is needed in order to fetch data from gitaly. Projects
+ # can be absent from commits in certain rare situations (like when
+ # viewing a MR of a deleted fork). In these cases, assume that the
+ # enriched data is not needed.
+ return self if project.blank? || fully_enriched?
+
+ # Batch load full Commits from the repository
+ # and map to a Hash of id => Commit
+ replacements = Hash[unenriched.map do |c|
+ [c.id, Commit.lazy(project, c.id)]
+ end.compact]
+
+ # Replace the commits, keeping the same order
+ @commits = @commits.map do |c|
+ replacements.fetch(c.id, c)
+ end
+
+ self
+ end
+
# Sets the pipeline status for every commit.
#
# Setting this status ahead of time removes the need for running a query for
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 5f66a661324..f97dc38dab7 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class CommitStatus < ActiveRecord::Base
+class CommitStatus < ApplicationRecord
include HasStatus
include Importable
include AfterCommitQueue
diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb
index 4e15b60ccd1..ab3d9e923c0 100644
--- a/app/models/concerns/atomic_internal_id.rb
+++ b/app/models/concerns/atomic_internal_id.rb
@@ -7,7 +7,7 @@
#
# For example, let's generate internal ids for Issue per Project:
# ```
-# class Issue < ActiveRecord::Base
+# class Issue < ApplicationRecord
# has_internal_id :iid, scope: :project, init: ->(s) { s.project.issues.maximum(:iid) }
# end
# ```
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 15d8d58b9b5..28ea51d6769 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -15,7 +15,7 @@ module CacheMarkdownField
# Increment this number every time the renderer changes its output
CACHE_COMMONMARK_VERSION_START = 10
- CACHE_COMMONMARK_VERSION = 14
+ CACHE_COMMONMARK_VERSION = 15
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
diff --git a/app/models/concerns/ignorable_column.rb b/app/models/concerns/ignorable_column.rb
index 5c1f7dfcd2a..3bec44dc79b 100644
--- a/app/models/concerns/ignorable_column.rb
+++ b/app/models/concerns/ignorable_column.rb
@@ -5,7 +5,7 @@
#
# Example:
#
-# class User < ActiveRecord::Base
+# class User < ApplicationRecord
# include IgnorableColumn
#
# ignore_column :updated_at
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 614c3242874..b140fca9b83 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -7,7 +7,7 @@
#
# Usage:
#
-# class Issue < ActiveRecord::Base
+# class Issue < ApplicationRecord
# include Participable
#
# # ...
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index d2ead7130e5..1ab3b3ddc46 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -7,7 +7,7 @@
#
# Example of use:
#
-# class Foo < ActiveRecord::Base
+# class Foo < ApplicationRecord
# include ReactiveCaching
#
# self.reactive_cache_key = ->(thing) { ["foo", thing.id] }
diff --git a/app/models/concerns/sha_attribute.rb b/app/models/concerns/sha_attribute.rb
index a479bef993c..70ac873a030 100644
--- a/app/models/concerns/sha_attribute.rb
+++ b/app/models/concerns/sha_attribute.rb
@@ -39,7 +39,7 @@ module ShaAttribute
end
def database_exists?
- ActiveRecord::Base.connection
+ ApplicationRecord.connection
true
rescue
diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb
index c9f5ba7793d..8f6a6244dd3 100644
--- a/app/models/concerns/strip_attribute.rb
+++ b/app/models/concerns/strip_attribute.rb
@@ -6,7 +6,7 @@
#
# Usage:
#
-# class Milestone < ActiveRecord::Base
+# class Milestone < ApplicationRecord
# strip_attributes :title
# end
#
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index cf057d774cf..39e12ac2b06 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ContainerRepository < ActiveRecord::Base
+class ContainerRepository < ApplicationRecord
include Gitlab::Utils::StrongMemoize
belongs_to :project
diff --git a/app/models/conversational_development_index/metric.rb b/app/models/conversational_development_index/metric.rb
index c54537572d6..b91123be87e 100644
--- a/app/models/conversational_development_index/metric.rb
+++ b/app/models/conversational_development_index/metric.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module ConversationalDevelopmentIndex
- class Metric < ActiveRecord::Base
+ class Metric < ApplicationRecord
include Presentable
self.table_name = 'conversational_development_index_metrics'
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index 71fd02fac86..15906ed8e06 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class DeployKeysProject < ActiveRecord::Base
+class DeployKeysProject < ApplicationRecord
belongs_to :project
belongs_to :deploy_key, inverse_of: :deploy_keys_projects
diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb
index e3524305346..b0e570f52ba 100644
--- a/app/models/deploy_token.rb
+++ b/app/models/deploy_token.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class DeployToken < ActiveRecord::Base
+class DeployToken < ApplicationRecord
include Expirable
include TokenAuthenticatable
include PolicyActor
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 428edfd88de..d847a0a11e4 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Deployment < ActiveRecord::Base
+class Deployment < ApplicationRecord
include AtomicInternalId
include IidRoutes
include AfterCommitQueue
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 87755cf3f3d..feabea9b8ba 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -77,8 +77,8 @@ class DiffNote < Note
def supports_suggestion?
return false unless noteable.supports_suggestion? && on_text?
# We don't want to trigger side-effects of `diff_file` call.
- return false unless file = fetch_diff_file
- return false unless line = file.line_for_position(self.original_position)
+ return false unless file = latest_diff_file
+ return false unless line = file.line_for_position(self.position)
line&.suggestible?
end
diff --git a/app/models/email.rb b/app/models/email.rb
index 7c33c5c7e64..0ddaa049c3b 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Email < ActiveRecord::Base
+class Email < ApplicationRecord
include Sortable
include Gitlab::SQL::Pattern
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 3d909cc8e5c..25373c7a1f7 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Environment < ActiveRecord::Base
+class Environment < ApplicationRecord
include Gitlab::Utils::StrongMemoize
# Used to generate random suffixes for the slug
LETTERS = 'a'..'z'
diff --git a/app/models/epic.rb b/app/models/epic.rb
index ccd10593434..3693db1de33 100644
--- a/app/models/epic.rb
+++ b/app/models/epic.rb
@@ -2,7 +2,7 @@
# Placeholder class for model that is implemented in EE
# It reserves '&' as a reference prefix, but the table does not exists in CE
-class Epic < ActiveRecord::Base
+class Epic < ApplicationRecord
def self.link_reference_pattern
nil
end
diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb
index 1e2bd3bda7f..70954bf8b05 100644
--- a/app/models/error_tracking/project_error_tracking_setting.rb
+++ b/app/models/error_tracking/project_error_tracking_setting.rb
@@ -1,10 +1,13 @@
# frozen_string_literal: true
module ErrorTracking
- class ProjectErrorTrackingSetting < ActiveRecord::Base
+ class ProjectErrorTrackingSetting < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include ReactiveCaching
+ SENTRY_API_ERROR_TYPE_MISSING_KEYS = 'missing_keys_in_sentry_response'
+ SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE = 'non_20x_response_from_sentry'
+
API_URL_PATH_REGEXP = %r{
\A
(?<prefix>/api/0/projects/+)
@@ -90,7 +93,9 @@ module ErrorTracking
{ issues: sentry_client.list_issues(**opts.symbolize_keys) }
end
rescue Sentry::Client::Error => e
- { error: e.message }
+ { error: e.message, error_type: SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE }
+ rescue Sentry::Client::MissingKeysError => e
+ { error: e.message, error_type: SENTRY_API_ERROR_TYPE_MISSING_KEYS }
end
# http://HOST/api/0/projects/ORG/PROJECT
diff --git a/app/models/event.rb b/app/models/event.rb
index 6a35bca72c5..593acf5edfe 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Event < ActiveRecord::Base
+class Event < ApplicationRecord
include Sortable
include IgnorableColumn
include FromUnion
diff --git a/app/models/fork_network.rb b/app/models/fork_network.rb
index 1b9bf93cbbc..0323a8d222a 100644
--- a/app/models/fork_network.rb
+++ b/app/models/fork_network.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ForkNetwork < ActiveRecord::Base
+class ForkNetwork < ApplicationRecord
belongs_to :root_project, class_name: 'Project'
has_many :fork_network_members
has_many :projects, through: :fork_network_members
diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb
index 36c66f21b0b..f18c306cf91 100644
--- a/app/models/fork_network_member.rb
+++ b/app/models/fork_network_member.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ForkNetworkMember < ActiveRecord::Base
+class ForkNetworkMember < ApplicationRecord
belongs_to :fork_network
belongs_to :project
belongs_to :forked_from_project, class_name: 'Project'
diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb
index 077afffd358..116beac5c2a 100644
--- a/app/models/gpg_key.rb
+++ b/app/models/gpg_key.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GpgKey < ActiveRecord::Base
+class GpgKey < ApplicationRecord
KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze
KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze
diff --git a/app/models/gpg_key_subkey.rb b/app/models/gpg_key_subkey.rb
index 440b588bc78..110bf451136 100644
--- a/app/models/gpg_key_subkey.rb
+++ b/app/models/gpg_key_subkey.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GpgKeySubkey < ActiveRecord::Base
+class GpgKeySubkey < ApplicationRecord
include ShaAttribute
sha_attribute :keyid
diff --git a/app/models/group_custom_attribute.rb b/app/models/group_custom_attribute.rb
index 22f14885657..5ac6e5f2550 100644
--- a/app/models/group_custom_attribute.rb
+++ b/app/models/group_custom_attribute.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GroupCustomAttribute < ActiveRecord::Base
+class GroupCustomAttribute < ApplicationRecord
belongs_to :group
validates :group, :key, :value, presence: true
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 1a8662db9fb..daf7ff4b771 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class WebHook < ActiveRecord::Base
+class WebHook < ApplicationRecord
include Sortable
attr_encrypted :token,
diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb
index 2d9f7594e8c..cfb1f3ec63b 100644
--- a/app/models/hooks/web_hook_log.rb
+++ b/app/models/hooks/web_hook_log.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class WebHookLog < ActiveRecord::Base
+class WebHookLog < ApplicationRecord
belongs_to :web_hook
serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize
diff --git a/app/models/identity.rb b/app/models/identity.rb
index acdde4f296b..8322b9bf35f 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Identity < ActiveRecord::Base
+class Identity < ApplicationRecord
include Sortable
include CaseSensitivity
diff --git a/app/models/identity/uniqueness_scopes.rb b/app/models/identity/uniqueness_scopes.rb
index 674b735903f..ce68371ae87 100644
--- a/app/models/identity/uniqueness_scopes.rb
+++ b/app/models/identity/uniqueness_scopes.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Identity < ActiveRecord::Base
+class Identity < ApplicationRecord
# This module and method are defined in a separate file to allow EE to
# redefine the `scopes` method before it is used in the `Identity` model.
module UniquenessScopes
diff --git a/app/models/import_export_upload.rb b/app/models/import_export_upload.rb
index f0cc5aafcd4..60f5491849a 100644
--- a/app/models/import_export_upload.rb
+++ b/app/models/import_export_upload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ImportExportUpload < ActiveRecord::Base
+class ImportExportUpload < ApplicationRecord
include WithUploads
include ObjectStorage::BackgroundMove
diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb
index e75c6eb2331..3f2d368a3f2 100644
--- a/app/models/internal_id.rb
+++ b/app/models/internal_id.rb
@@ -15,7 +15,7 @@
# In order to leverage InternalId for other usages, the idea is to
# * Add `usage` value to enum
# * (Optionally) add columns to `internal_ids` if needed for scope.
-class InternalId < ActiveRecord::Base
+class InternalId < ApplicationRecord
belongs_to :project
belongs_to :namespace
diff --git a/app/models/issue.rb b/app/models/issue.rb
index deab53d25e7..97c6dcc4745 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -2,7 +2,7 @@
require 'carrierwave/orm/activerecord'
-class Issue < ActiveRecord::Base
+class Issue < ApplicationRecord
include AtomicInternalId
include IidRoutes
include Issuable
diff --git a/app/models/issue/metrics.rb b/app/models/issue/metrics.rb
index 0f5ee957ec9..8010cbc3d78 100644
--- a/app/models/issue/metrics.rb
+++ b/app/models/issue/metrics.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Issue::Metrics < ActiveRecord::Base
+class Issue::Metrics < ApplicationRecord
belongs_to :issue
def record!
diff --git a/app/models/issue_assignee.rb b/app/models/issue_assignee.rb
index 400c0256945..fbd9be1fb43 100644
--- a/app/models/issue_assignee.rb
+++ b/app/models/issue_assignee.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class IssueAssignee < ActiveRecord::Base
+class IssueAssignee < ApplicationRecord
belongs_to :issue
belongs_to :assignee, class_name: "User", foreign_key: :user_id
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 8f93418b88b..b097be8cc89 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -2,7 +2,7 @@
require 'digest/md5'
-class Key < ActiveRecord::Base
+class Key < ApplicationRecord
include AfterCommitQueue
include Sortable
diff --git a/app/models/label.rb b/app/models/label.rb
index e13a2322c6b..c7fff0d393e 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Label < ActiveRecord::Base
+class Label < ApplicationRecord
include CacheMarkdownField
include Referable
include Subscribable
diff --git a/app/models/label_link.rb b/app/models/label_link.rb
index 1d93a55e8e9..ffc0afd8e85 100644
--- a/app/models/label_link.rb
+++ b/app/models/label_link.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LabelLink < ActiveRecord::Base
+class LabelLink < ApplicationRecord
include Importable
belongs_to :target, polymorphic: true, inverse_of: :label_links # rubocop:disable Cop/PolymorphicAssociations
diff --git a/app/models/label_priority.rb b/app/models/label_priority.rb
index 8ed8bb7577f..8f8f36efbfe 100644
--- a/app/models/label_priority.rb
+++ b/app/models/label_priority.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LabelPriority < ActiveRecord::Base
+class LabelPriority < ApplicationRecord
belongs_to :project
belongs_to :label
diff --git a/app/models/lfs_file_lock.rb b/app/models/lfs_file_lock.rb
index 431d37e12e9..624b1d02e1a 100644
--- a/app/models/lfs_file_lock.rb
+++ b/app/models/lfs_file_lock.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LfsFileLock < ActiveRecord::Base
+class LfsFileLock < ApplicationRecord
belongs_to :project
belongs_to :user
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 69c563545bb..e1aac691a64 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LfsObject < ActiveRecord::Base
+class LfsObject < ApplicationRecord
include AfterCommitQueue
include ObjectStorage::BackgroundMove
diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb
index 353602800d7..f9afb18c1d7 100644
--- a/app/models/lfs_objects_project.rb
+++ b/app/models/lfs_objects_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LfsObjectsProject < ActiveRecord::Base
+class LfsObjectsProject < ApplicationRecord
belongs_to :project
belongs_to :lfs_object
diff --git a/app/models/list.rb b/app/models/list.rb
index 682af761ba0..17b1a8510cf 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class List < ActiveRecord::Base
+class List < ApplicationRecord
belongs_to :board
belongs_to :label
diff --git a/app/models/member.rb b/app/models/member.rb
index 5dbc0c2eec9..8a06bff51b5 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Member < ActiveRecord::Base
+class Member < ApplicationRecord
include AfterCommitQueue
include Sortable
include Importable
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 2c9e1ba1d80..510f856087d 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -14,6 +14,8 @@ class GroupMember < Member
scope :in_groups, ->(groups) { where(source_id: groups.select(:id)) }
+ scope :count_users_by_group_id, -> { joins(:user).group(:source_id).count }
+
after_create :update_two_factor_requirement, unless: :invite?
after_destroy :update_two_factor_requirement, unless: :invite?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5f6d5095bcc..f6b83453c2a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequest < ActiveRecord::Base
+class MergeRequest < ApplicationRecord
include AtomicInternalId
include IidRoutes
include Issuable
@@ -66,7 +66,10 @@ class MergeRequest < ActiveRecord::Base
has_many :cached_closes_issues, through: :merge_requests_closing_issues, source: :issue
has_many :merge_request_pipelines, foreign_key: 'merge_request_id', class_name: 'Ci::Pipeline'
+ has_many :suggestions, through: :notes
+ has_many :merge_request_assignees
+ # Will be deprecated at https://gitlab.com/gitlab-org/gitlab-ce/issues/59457
belongs_to :assignee, class_name: "User"
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
@@ -76,6 +79,10 @@ class MergeRequest < ActiveRecord::Base
after_update :reload_diff_if_branch_changed
after_save :ensure_metrics
+ # Required until the codebase starts using this relation for single or multiple assignees.
+ # TODO: Remove at gitlab-ee#2004 implementation.
+ after_save :refresh_merge_request_assignees, if: :assignee_id_changed?
+
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
attr_accessor :allow_broken
@@ -675,6 +682,15 @@ class MergeRequest < ActiveRecord::Base
merge_request_diff || create_merge_request_diff
end
+ def refresh_merge_request_assignees
+ transaction do
+ # Using it instead relation.delete_all in order to avoid adding a
+ # dependent: :delete_all (we already have foreign key cascade deletion).
+ MergeRequestAssignee.where(merge_request_id: self).delete_all
+ merge_request_assignees.create(user_id: assignee_id) if assignee_id
+ end
+ end
+
def create_merge_request_diff
fetch_ref!
@@ -791,8 +807,7 @@ class MergeRequest < ActiveRecord::Base
end
def mergeable_to_ref?
- return false if merged?
- return false if broken?
+ return false unless mergeable_state?(skip_ci_check: true, skip_discussions_check: true)
# Given the `merge_ref_path` will have the same
# state the `target_branch` would have. Ideally
@@ -1113,6 +1128,10 @@ class MergeRequest < ActiveRecord::Base
"refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/merge"
end
+ def self.merge_request_ref?(ref)
+ ref.start_with?("refs/#{Repository::REF_MERGE_REQUEST}/")
+ end
+
def in_locked_state
begin
lock_mr
@@ -1323,7 +1342,7 @@ class MergeRequest < ActiveRecord::Base
end
def has_commits?
- merge_request_diff && commits_count > 0
+ merge_request_diff && commits_count.to_i > 0
end
def has_no_commits?
diff --git a/app/models/merge_request/metrics.rb b/app/models/merge_request/metrics.rb
index 65e94a97b0a..05f8e18a2c1 100644
--- a/app/models/merge_request/metrics.rb
+++ b/app/models/merge_request/metrics.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequest::Metrics < ActiveRecord::Base
+class MergeRequest::Metrics < ApplicationRecord
belongs_to :merge_request
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :pipeline_id
belongs_to :latest_closed_by, class_name: 'User'
diff --git a/app/models/merge_request_assignee.rb b/app/models/merge_request_assignee.rb
new file mode 100644
index 00000000000..f0e6be51b7f
--- /dev/null
+++ b/app/models/merge_request_assignee.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class MergeRequestAssignee < ApplicationRecord
+ belongs_to :merge_request
+ belongs_to :assignee, class_name: "User", foreign_key: :user_id
+end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 98db1bf7de7..ac8d3b98266 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestDiff < ActiveRecord::Base
+class MergeRequestDiff < ApplicationRecord
include Sortable
include Importable
include ManualInverseAssociation
@@ -12,6 +12,10 @@ class MergeRequestDiff < ActiveRecord::Base
# Don't display more than 100 commits at once
COMMITS_SAFE_SIZE = 100
+ # Applies to closed or merged MRs when determining whether to migrate their
+ # diffs to external storage
+ EXTERNAL_DIFF_CUTOFF = 7.days.freeze
+
belongs_to :merge_request
manual_inverse_association :merge_request, :merge_request_diff
@@ -48,6 +52,81 @@ class MergeRequestDiff < ActiveRecord::Base
end
scope :recent, -> { order(id: :desc).limit(100) }
+ scope :files_in_database, -> { where(stored_externally: [false, nil]) }
+
+ scope :not_latest_diffs, -> do
+ merge_requests = MergeRequest.arel_table
+ mr_diffs = arel_table
+
+ join_condition = merge_requests[:id].eq(mr_diffs[:merge_request_id])
+ .and(mr_diffs[:id].not_eq(merge_requests[:latest_merge_request_diff_id]))
+
+ arel_join = mr_diffs.join(merge_requests).on(join_condition)
+ joins(arel_join.join_sources)
+ end
+
+ scope :old_merged_diffs, -> (before) do
+ merge_requests = MergeRequest.arel_table
+ mr_metrics = MergeRequest::Metrics.arel_table
+ mr_diffs = arel_table
+
+ mr_join = mr_diffs
+ .join(merge_requests)
+ .on(mr_diffs[:merge_request_id].eq(merge_requests[:id]))
+
+ metrics_join_condition = mr_diffs[:merge_request_id]
+ .eq(mr_metrics[:merge_request_id])
+ .and(mr_metrics[:merged_at].not_eq(nil))
+
+ metrics_join = mr_diffs.join(mr_metrics).on(metrics_join_condition)
+
+ condition = MergeRequest.arel_table[:state].eq(:merged)
+ .and(MergeRequest::Metrics.arel_table[:merged_at].lteq(before))
+ .and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil))
+
+ joins(metrics_join.join_sources, mr_join.join_sources).where(condition)
+ end
+
+ scope :old_closed_diffs, -> (before) do
+ condition = MergeRequest.arel_table[:state].eq(:closed)
+ .and(MergeRequest::Metrics.arel_table[:latest_closed_at].lteq(before))
+
+ joins(merge_request: :metrics).where(condition)
+ end
+
+ def self.ids_for_external_storage_migration(limit:)
+ # No point doing any work unless the feature is enabled
+ return [] unless Gitlab.config.external_diffs.enabled
+
+ case Gitlab.config.external_diffs.when
+ when 'always'
+ files_in_database.limit(limit).pluck(:id)
+ when 'outdated'
+ # Outdated is too complex to be a single SQL query, so split into three
+ before = EXTERNAL_DIFF_CUTOFF.ago
+
+ ids = files_in_database
+ .old_merged_diffs(before)
+ .limit(limit)
+ .pluck(:id)
+
+ return ids if ids.size >= limit
+
+ ids += files_in_database
+ .old_closed_diffs(before)
+ .limit(limit - ids.size)
+ .pluck(:id)
+
+ return ids if ids.size >= limit
+
+ ids + files_in_database
+ .not_latest_diffs
+ .limit(limit - ids.size)
+ .pluck(:id)
+ else
+ []
+ end
+ end
mount_uploader :external_diff, ExternalDiffUploader
@@ -55,7 +134,7 @@ class MergeRequestDiff < ActiveRecord::Base
# It allows you to override variables like head_commit_sha before getting diff.
after_create :save_git_content, unless: :importing?
- after_save :update_external_diff_store, if: :external_diff_changed?
+ after_save :update_external_diff_store, if: -> { !importing? && external_diff_changed? }
def self.find_by_diff_refs(diff_refs)
find_by(start_commit_sha: diff_refs.start_sha, head_commit_sha: diff_refs.head_sha, base_commit_sha: diff_refs.base_sha)
@@ -294,6 +373,23 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
+ # Transactionally migrate the current merge_request_diff_files entries to
+ # external storage. If external storage isn't an option for this diff, the
+ # method is a no-op.
+ def migrate_files_to_external_storage!
+ return if stored_externally? || !use_external_diff? || merge_request_diff_files.count == 0
+
+ rows = build_merge_request_diff_files(merge_request_diff_files)
+
+ transaction do
+ MergeRequestDiffFile.where(merge_request_diff_id: id).delete_all
+ create_merge_request_diff_files(rows)
+ save!
+ end
+
+ merge_request_diff_files.reload
+ end
+
private
def encode_in_base64?(diff_text)
@@ -301,20 +397,7 @@ class MergeRequestDiff < ActiveRecord::Base
diff_text.include?("\0")
end
- def create_merge_request_diff_files(diffs)
- rows =
- if has_attribute?(:external_diff) && Gitlab.config.external_diffs.enabled
- build_external_merge_request_diff_files(diffs)
- else
- build_merge_request_diff_files(diffs)
- end
-
- # Faster inserts
- Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
- end
-
- def build_external_merge_request_diff_files(diffs)
- rows = build_merge_request_diff_files(diffs)
+ def build_external_merge_request_diff_files(rows)
tempfile = build_external_diff_tempfile(rows)
self.external_diff = tempfile
@@ -325,16 +408,21 @@ class MergeRequestDiff < ActiveRecord::Base
tempfile&.unlink
end
+ def create_merge_request_diff_files(rows)
+ rows = build_external_merge_request_diff_files(rows) if use_external_diff?
+
+ # Faster inserts
+ Gitlab::Database.bulk_insert('merge_request_diff_files', rows)
+ end
+
def build_external_diff_tempfile(rows)
Tempfile.open(external_diff.filename) do |file|
- rows.inject(0) do |offset, row|
+ rows.each do |row|
data = row.delete(:diff)
- row[:external_diff_offset] = offset
- row[:external_diff_size] = data.size
+ row[:external_diff_offset] = file.pos
+ row[:external_diff_size] = data.bytesize
file.write(data)
-
- offset + data.size
end
file
@@ -361,6 +449,47 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
+ def use_external_diff?
+ return false unless has_attribute?(:external_diff)
+ return false unless Gitlab.config.external_diffs.enabled
+
+ case Gitlab.config.external_diffs.when
+ when 'always'
+ true
+ when 'outdated'
+ outdated_by_merge? || outdated_by_closure? || old_version?
+ else
+ false # Disable external diffs if misconfigured
+ end
+ end
+
+ def outdated_by_merge?
+ return false unless merge_request&.metrics&.merged_at
+
+ merge_request.merged? && merge_request.metrics.merged_at < EXTERNAL_DIFF_CUTOFF.ago
+ end
+
+ def outdated_by_closure?
+ return false unless merge_request&.metrics&.latest_closed_at
+
+ merge_request.closed? && merge_request.metrics.latest_closed_at < EXTERNAL_DIFF_CUTOFF.ago
+ end
+
+ # We can't rely on `merge_request.latest_merge_request_diff_id` because that
+ # may have been changed in `save_git_content` without being reflected in
+ # the association's instance. This query is always subject to races, but
+ # the worst case is that we *don't* make a diff external when we could. The
+ # background worker will make it external at a later date.
+ def old_version?
+ latest_id = MergeRequest
+ .where(id: merge_request_id)
+ .limit(1)
+ .pluck(:latest_merge_request_diff_id)
+ .first
+
+ self.id != latest_id
+ end
+
def load_diffs(options)
# Ensure all diff files operate on the same external diff file instance if
# present. This reduces file open/close overhead.
@@ -394,7 +523,8 @@ class MergeRequestDiff < ActiveRecord::Base
if diff_collection.any?
new_attributes[:state] = :collected
- create_merge_request_diff_files(diff_collection)
+ rows = build_merge_request_diff_files(diff_collection)
+ create_merge_request_diff_files(rows)
end
# Set our state to 'overflow' to make the #empty? and #collected?
diff --git a/app/models/merge_request_diff_commit.rb b/app/models/merge_request_diff_commit.rb
index 4ad3690512d..b897bbc8cf5 100644
--- a/app/models/merge_request_diff_commit.rb
+++ b/app/models/merge_request_diff_commit.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestDiffCommit < ActiveRecord::Base
+class MergeRequestDiffCommit < ApplicationRecord
include ShaAttribute
belongs_to :merge_request_diff
diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb
index 16ec4ed470f..01ee82ae398 100644
--- a/app/models/merge_request_diff_file.rb
+++ b/app/models/merge_request_diff_file.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestDiffFile < ActiveRecord::Base
+class MergeRequestDiffFile < ApplicationRecord
include Gitlab::EncodingHelper
include DiffFile
diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb
index 242b65bedc0..61af50841ee 100644
--- a/app/models/merge_requests_closing_issues.rb
+++ b/app/models/merge_requests_closing_issues.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestsClosingIssues < ActiveRecord::Base
+class MergeRequestsClosingIssues < ApplicationRecord
belongs_to :merge_request
belongs_to :issue
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index a3831ae3fa8..b4aad9e512e 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Milestone < ActiveRecord::Base
+class Milestone < ApplicationRecord
# Represents a "No Milestone" state used for filtering Issues and Merge
# Requests that have no milestone assigned.
MilestoneStruct = Struct.new(:title, :name, :id)
diff --git a/app/models/note.rb b/app/models/note.rb
index 2c9980b1a0d..081d6f91230 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -3,7 +3,7 @@
# A note on the root of an issue, merge request, commit, or snippet.
#
# A note of this type is never resolvable.
-class Note < ActiveRecord::Base
+class Note < ApplicationRecord
extend ActiveModel::Naming
include Participable
include Mentionable
diff --git a/app/models/note_diff_file.rb b/app/models/note_diff_file.rb
index e369122003e..9afb94c869a 100644
--- a/app/models/note_diff_file.rb
+++ b/app/models/note_diff_file.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class NoteDiffFile < ActiveRecord::Base
+class NoteDiffFile < ApplicationRecord
include DiffFile
scope :for_commit_or_unresolved, -> do
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index e82eaf4e069..61af5c09ae4 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class NotificationSetting < ActiveRecord::Base
+class NotificationSetting < ApplicationRecord
include IgnorableColumn
ignore_column :events
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 7a33ade826b..82901ceec01 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class PagesDomain < ActiveRecord::Base
+class PagesDomain < ApplicationRecord
VERIFICATION_KEY = 'gitlab-pages-verification-code'.freeze
VERIFICATION_THRESHOLD = 3.days.freeze
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index ed78a46eaf3..570112b63b7 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class PersonalAccessToken < ActiveRecord::Base
+class PersonalAccessToken < ApplicationRecord
include Expirable
include IgnorableColumn
include TokenAuthenticatable
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
index 4635fc72dc7..35c718365b4 100644
--- a/app/models/pool_repository.rb
+++ b/app/models/pool_repository.rb
@@ -3,7 +3,7 @@
# The PoolRepository model is the database equivalent of an ObjectPool for Gitaly
# That is; PoolRepository is the record in the database, ObjectPool is the
# repository on disk
-class PoolRepository < ActiveRecord::Base
+class PoolRepository < ApplicationRecord
include Shardable
include AfterCommitQueue
diff --git a/app/models/postgresql/replication_slot.rb b/app/models/postgresql/replication_slot.rb
index e264fe88e47..74ccf23cf69 100644
--- a/app/models/postgresql/replication_slot.rb
+++ b/app/models/postgresql/replication_slot.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Postgresql
- class ReplicationSlot < ActiveRecord::Base
+ class ReplicationSlot < ApplicationRecord
self.table_name = 'pg_replication_slots'
# Returns true if there are any replication slots in use.
diff --git a/app/models/programming_language.rb b/app/models/programming_language.rb
index 5f0f313b7f9..375fbe9b5a9 100644
--- a/app/models/programming_language.rb
+++ b/app/models/programming_language.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProgrammingLanguage < ActiveRecord::Base
+class ProgrammingLanguage < ApplicationRecord
validates :name, presence: true
validates :color, allow_blank: false, color: true
diff --git a/app/models/project.rb b/app/models/project.rb
index 611c64c8f49..82c2f9090c8 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2,7 +2,7 @@
require 'carrierwave/orm/activerecord'
-class Project < ActiveRecord::Base
+class Project < ApplicationRecord
include Gitlab::ConfigHelper
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
@@ -459,14 +459,41 @@ class Project < ActiveRecord::Base
# Returns a collection of projects that is either public or visible to the
# logged in user.
- def self.public_or_visible_to_user(user = nil)
- if user
- where('EXISTS (?) OR projects.visibility_level IN (?)',
- user.authorizations_for_projects,
- Gitlab::VisibilityLevel.levels_for_user(user))
- else
- public_to_user
- end
+ #
+ # requested_visiblity_levels: Normally all projects that are visible
+ # to the user (e.g. internal and public) are queried, but this
+ # parameter allows the caller to narrow the search space to optimize
+ # database queries. For instance, a caller may only want to see
+ # internal projects. Instead of querying for internal and public
+ # projects and throwing away public projects, this parameter allows
+ # the query to be targeted for only internal projects.
+ def self.public_or_visible_to_user(user = nil, requested_visibility_levels = [])
+ return public_to_user unless user
+
+ visible_levels = Gitlab::VisibilityLevel.levels_for_user(user)
+ include_private = true
+ requested_visibility_levels = Array(requested_visibility_levels)
+
+ if requested_visibility_levels.present?
+ visible_levels &= requested_visibility_levels
+ include_private = requested_visibility_levels.include?(Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ public_or_internal_rel =
+ if visible_levels.present?
+ where('projects.visibility_level IN (?)', visible_levels)
+ else
+ Project.none
+ end
+
+ private_rel =
+ if include_private
+ where('EXISTS (?)', user.authorizations_for_projects)
+ else
+ Project.none
+ end
+
+ public_or_internal_rel.or(private_rel)
end
# project features may be "disabled", "internal", "enabled" or "public". If "internal",
@@ -2003,12 +2030,8 @@ class Project < ActiveRecord::Base
@storage = nil if storage_version_changed?
end
- def gl_repository(is_wiki:)
- Gitlab::GlRepository.gl_repository(self, is_wiki)
- end
-
- def reference_counter(wiki: false)
- Gitlab::ReferenceCounter.new(gl_repository(is_wiki: wiki))
+ def reference_counter(type: Gitlab::GlRepository::PROJECT)
+ Gitlab::ReferenceCounter.new(type.identifier_for_subject(self))
end
def badges
@@ -2152,7 +2175,7 @@ class Project < ActiveRecord::Base
end
def wiki_reference_count
- reference_counter(wiki: true).value
+ reference_counter(type: Gitlab::GlRepository::WIKI).value
end
def check_repository_absence!
diff --git a/app/models/project_authorization.rb b/app/models/project_authorization.rb
index 2c590008db2..f95d3ab54e2 100644
--- a/app/models/project_authorization.rb
+++ b/app/models/project_authorization.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectAuthorization < ActiveRecord::Base
+class ProjectAuthorization < ApplicationRecord
include FromUnion
belongs_to :user
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index e353a6443c4..f972c40f317 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectAutoDevops < ActiveRecord::Base
+class ProjectAutoDevops < ApplicationRecord
belongs_to :project
enum deploy_strategy: {
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index 1dad235cc2b..1414164b703 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectCiCdSetting < ActiveRecord::Base
+class ProjectCiCdSetting < ApplicationRecord
belongs_to :project, inverse_of: :ci_cd_settings
# The version of the schema that first introduced this model/table.
diff --git a/app/models/project_custom_attribute.rb b/app/models/project_custom_attribute.rb
index 4e767cb3b26..b0da586988a 100644
--- a/app/models/project_custom_attribute.rb
+++ b/app/models/project_custom_attribute.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectCustomAttribute < ActiveRecord::Base
+class ProjectCustomAttribute < ApplicationRecord
belongs_to :project
validates :project, :key, :value, presence: true
diff --git a/app/models/project_daily_statistic.rb b/app/models/project_daily_statistic.rb
index ff115dd010f..5ee11ab186e 100644
--- a/app/models/project_daily_statistic.rb
+++ b/app/models/project_daily_statistic.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectDailyStatistic < ActiveRecord::Base
+class ProjectDailyStatistic < ApplicationRecord
belongs_to :project
scope :of_project, -> (project) { where(project: project) }
diff --git a/app/models/project_deploy_token.rb b/app/models/project_deploy_token.rb
index 719c492a1ff..a55667496fb 100644
--- a/app/models/project_deploy_token.rb
+++ b/app/models/project_deploy_token.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectDeployToken < ActiveRecord::Base
+class ProjectDeployToken < ApplicationRecord
belongs_to :project
belongs_to :deploy_token, inverse_of: :project_deploy_tokens
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index e6787236c4e..0542581c6e0 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectFeature < ActiveRecord::Base
+class ProjectFeature < ApplicationRecord
# == Project features permissions
#
# Grants access level to project tools
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index bc3759142ae..58b555c3581 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectGroupLink < ActiveRecord::Base
+class ProjectGroupLink < ApplicationRecord
include Expirable
GUEST = 10
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index aa0c121fe99..580e8dfd833 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -2,7 +2,7 @@
require 'carrierwave/orm/activerecord'
-class ProjectImportData < ActiveRecord::Base
+class ProjectImportData < ApplicationRecord
belongs_to :project, inverse_of: :import_data
attr_encrypted :credentials,
key: Settings.attr_encrypted_db_key_base,
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index 488f0cb5971..1605345efd5 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectImportState < ActiveRecord::Base
+class ProjectImportState < ApplicationRecord
include AfterCommitQueue
self.table_name = "project_mirror_data"
diff --git a/app/models/project_repository.rb b/app/models/project_repository.rb
index 38913f3f2f5..092efabd73f 100644
--- a/app/models/project_repository.rb
+++ b/app/models/project_repository.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectRepository < ActiveRecord::Base
+class ProjectRepository < ApplicationRecord
include Shardable
belongs_to :project, inverse_of: :project_repository
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 781a197d56f..c020e72908c 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProjectStatistics < ActiveRecord::Base
+class ProjectStatistics < ApplicationRecord
belongs_to :project
belongs_to :namespace
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 6ea0716c192..268706a6aea 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -59,7 +59,7 @@ class ProjectWiki
# Returns the Gitlab::Git::Wiki object.
def wiki
@wiki ||= begin
- gl_repository = Gitlab::GlRepository.gl_repository(project, true)
+ gl_repository = Gitlab::GlRepository::WIKI.identifier_for_subject(project)
raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository, full_path)
create_repo!(raw_repository) unless raw_repository.exists?
@@ -151,7 +151,7 @@ class ProjectWiki
end
def repository
- @repository ||= Repository.new(full_path, @project, disk_path: disk_path, is_wiki: true)
+ @repository ||= Repository.new(full_path, @project, disk_path: disk_path, repo_type: Gitlab::GlRepository::WIKI)
end
def default_branch
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index 5594594a48d..62090444f79 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class PrometheusMetric < ActiveRecord::Base
+class PrometheusMetric < ApplicationRecord
belongs_to :project, validate: true, inverse_of: :prometheus_metrics
enum group: {
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 597431be65a..ee0c94c20af 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProtectedBranch < ActiveRecord::Base
+class ProtectedBranch < ApplicationRecord
include ProtectedRef
protected_ref_access_levels :merge, :push
diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb
index b0d5c64e931..de240e40316 100644
--- a/app/models/protected_branch/merge_access_level.rb
+++ b/app/models/protected_branch/merge_access_level.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base
+class ProtectedBranch::MergeAccessLevel < ApplicationRecord
include ProtectedBranchAccess
end
diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb
index b2a88229853..bde1d29ad7f 100644
--- a/app/models/protected_branch/push_access_level.rb
+++ b/app/models/protected_branch/push_access_level.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-class ProtectedBranch::PushAccessLevel < ActiveRecord::Base
+class ProtectedBranch::PushAccessLevel < ApplicationRecord
include ProtectedBranchAccess
end
diff --git a/app/models/protected_tag.rb b/app/models/protected_tag.rb
index d28ebabfe49..6b507429e57 100644
--- a/app/models/protected_tag.rb
+++ b/app/models/protected_tag.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProtectedTag < ActiveRecord::Base
+class ProtectedTag < ApplicationRecord
include ProtectedRef
validates :name, uniqueness: { scope: :project_id }
diff --git a/app/models/protected_tag/create_access_level.rb b/app/models/protected_tag/create_access_level.rb
index b06e55fb5dd..9fcfa7646a2 100644
--- a/app/models/protected_tag/create_access_level.rb
+++ b/app/models/protected_tag/create_access_level.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class ProtectedTag::CreateAccessLevel < ActiveRecord::Base
+class ProtectedTag::CreateAccessLevel < ApplicationRecord
include ProtectedTagAccess
def check_access(user)
diff --git a/app/models/push_event_payload.rb b/app/models/push_event_payload.rb
index c7769edf055..537859ec7b7 100644
--- a/app/models/push_event_payload.rb
+++ b/app/models/push_event_payload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class PushEventPayload < ActiveRecord::Base
+class PushEventPayload < ApplicationRecord
include ShaAttribute
belongs_to :event, inverse_of: :push_event_payload
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index c6bd4bb6dfa..2e4769364c6 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RedirectRoute < ActiveRecord::Base
+class RedirectRoute < ApplicationRecord
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
validates :source, presence: true
diff --git a/app/models/release.rb b/app/models/release.rb
index 0dae5c90394..746fc31a038 100644
--- a/app/models/release.rb
+++ b/app/models/release.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Release < ActiveRecord::Base
+class Release < ApplicationRecord
include CacheMarkdownField
include Gitlab::Utils::StrongMemoize
diff --git a/app/models/releases/link.rb b/app/models/releases/link.rb
index 6c507c47752..36ec33d3e3e 100644
--- a/app/models/releases/link.rb
+++ b/app/models/releases/link.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Releases
- class Link < ActiveRecord::Base
+ class Link < ApplicationRecord
self.table_name = 'release_links'
belongs_to :release
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index 5eba7ddd75c..5610cfe0f24 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RemoteMirror < ActiveRecord::Base
+class RemoteMirror < ApplicationRecord
include AfterCommitQueue
include MirrorAuthentication
diff --git a/app/models/repository.rb b/app/models/repository.rb
index ff355295862..574ce12b309 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -19,7 +19,7 @@ class Repository
include Gitlab::RepositoryCacheAdapter
- attr_accessor :full_path, :disk_path, :project, :is_wiki
+ attr_accessor :full_path, :disk_path, :project, :repo_type
delegate :ref_name_for_sha, to: :raw_repository
delegate :bundle_to_disk, to: :raw_repository
@@ -60,12 +60,12 @@ class Repository
xcode_config: :xcode_project?
}.freeze
- def initialize(full_path, project, disk_path: nil, is_wiki: false)
+ def initialize(full_path, project, disk_path: nil, repo_type: Gitlab::GlRepository::PROJECT)
@full_path = full_path
@disk_path = disk_path || full_path
@project = project
@commit_cache = {}
- @is_wiki = is_wiki
+ @repo_type = repo_type
end
def ==(other)
@@ -1112,7 +1112,7 @@ class Repository
def initialize_raw_repository
Gitlab::Git::Repository.new(project.repository_storage,
disk_path + '.git',
- Gitlab::GlRepository.gl_repository(project, is_wiki),
+ repo_type.identifier_for_subject(project),
project.full_path)
end
end
diff --git a/app/models/repository_language.rb b/app/models/repository_language.rb
index b18142a2ac4..e6867f905e2 100644
--- a/app/models/repository_language.rb
+++ b/app/models/repository_language.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class RepositoryLanguage < ActiveRecord::Base
+class RepositoryLanguage < ApplicationRecord
belongs_to :project
belongs_to :programming_language
diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb
index 3fd96b9dc18..f2c7cb6a65d 100644
--- a/app/models/resource_label_event.rb
+++ b/app/models/resource_label_event.rb
@@ -2,7 +2,7 @@
# This model is not used yet, it will be used for:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/48483
-class ResourceLabelEvent < ActiveRecord::Base
+class ResourceLabelEvent < ApplicationRecord
include Importable
include Gitlab::Utils::StrongMemoize
include CacheMarkdownField
diff --git a/app/models/route.rb b/app/models/route.rb
index 4b23dfa5778..7e3db54d4fe 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Route < ActiveRecord::Base
+class Route < ApplicationRecord
include CaseSensitivity
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb
index 6caab24143b..0427d5b9ca7 100644
--- a/app/models/sent_notification.rb
+++ b/app/models/sent_notification.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class SentNotification < ActiveRecord::Base
+class SentNotification < ApplicationRecord
serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
belongs_to :project
diff --git a/app/models/service.rb b/app/models/service.rb
index da523bfa426..c6d5eb353dc 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -2,7 +2,7 @@
# To add new service you should build a class inherited from Service
# and implement a set of methods
-class Service < ActiveRecord::Base
+class Service < ApplicationRecord
include Sortable
include Importable
include ProjectServicesLoggable
diff --git a/app/models/shard.rb b/app/models/shard.rb
index e39d4232486..335a279c6aa 100644
--- a/app/models/shard.rb
+++ b/app/models/shard.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Shard < ActiveRecord::Base
+class Shard < ApplicationRecord
# Store shard names from the configuration file in the database. This is not a
# list of active shards - we just want to assign an immutable, unique ID to
# every shard name for easy indexing / referencing.
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index f23ddd64fe3..f4fdac2558c 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Snippet < ActiveRecord::Base
+class Snippet < ApplicationRecord
include Gitlab::VisibilityLevel
include Redactable
include CacheMarkdownField
diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb
index ef3f974b959..5b9ece8373f 100644
--- a/app/models/spam_log.rb
+++ b/app/models/spam_log.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class SpamLog < ActiveRecord::Base
+class SpamLog < ApplicationRecord
belongs_to :user
validates :user, presence: true
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index 0f6ee0ddf7e..24a2b8b5167 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Subscription < ActiveRecord::Base
+class Subscription < ApplicationRecord
belongs_to :user
belongs_to :project
belongs_to :subscribable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
index 09034646bff..22e2f11230d 100644
--- a/app/models/suggestion.rb
+++ b/app/models/suggestion.rb
@@ -1,11 +1,19 @@
# frozen_string_literal: true
class Suggestion < ApplicationRecord
+ include Suggestible
+
belongs_to :note, inverse_of: :suggestions
validates :note, presence: true
validates :commit_id, presence: true, if: :applied?
- delegate :original_position, :position, :noteable, to: :note
+ delegate :position, :noteable, to: :note
+
+ scope :active, -> { where(outdated: false) }
+
+ def diff_file
+ note.latest_diff_file
+ end
def project
noteable.source_project
@@ -19,37 +27,37 @@ class Suggestion < ApplicationRecord
position.file_path
end
- # For now, suggestions only serve as a way to send patches that
- # will change a single line (being able to apply multiple in the same place),
- # which explains `from_line` and `to_line` being the same line.
- # We'll iterate on that in https://gitlab.com/gitlab-org/gitlab-ce/issues/53310
- # when allowing multi-line suggestions.
- def from_line
- position.new_line
- end
- alias_method :to_line, :from_line
-
- def from_original_line
- original_position.new_line
- end
- alias_method :to_original_line, :from_original_line
-
# `from_line_index` and `to_line_index` represents diff/blob line numbers in
# index-like way (N-1).
def from_line_index
from_line - 1
end
- alias_method :to_line_index, :from_line_index
- def appliable?
- return false unless note.supports_suggestion?
+ def to_line_index
+ to_line - 1
+ end
+ def appliable?(cached: true)
!applied? &&
noteable.opened? &&
+ !outdated?(cached: cached) &&
+ note.supports_suggestion? &&
different_content? &&
note.active?
end
+ # Overwrites outdated column
+ def outdated?(cached: true)
+ return super() if cached
+ return true unless diff_file
+
+ from_content != fetch_from_content
+ end
+
+ def target_line
+ position.new_line
+ end
+
private
def different_content?
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index d555ebe5322..55da37c9545 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class SystemNoteMetadata < ActiveRecord::Base
+class SystemNoteMetadata < ApplicationRecord
# These notes's action text might contain a reference that is external.
# We should always force a deep validation upon references that are found
# in this note type.
diff --git a/app/models/term_agreement.rb b/app/models/term_agreement.rb
index 9b3c8ac68bd..a4a9dc10282 100644
--- a/app/models/term_agreement.rb
+++ b/app/models/term_agreement.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class TermAgreement < ActiveRecord::Base
+class TermAgreement < ApplicationRecord
belongs_to :term, class_name: 'ApplicationSetting::Term'
belongs_to :user
diff --git a/app/models/timelog.rb b/app/models/timelog.rb
index e04c644a53a..048134fbf04 100644
--- a/app/models/timelog.rb
+++ b/app/models/timelog.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Timelog < ActiveRecord::Base
+class Timelog < ApplicationRecord
validates :time_spent, :user, presence: true
validate :issuable_id_is_present
diff --git a/app/models/todo.rb b/app/models/todo.rb
index 2b0dee875a3..5dcc3e9945a 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Todo < ActiveRecord::Base
+class Todo < ApplicationRecord
include Sortable
include FromUnion
diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb
index 7b22e8cb760..810dee672b2 100644
--- a/app/models/trending_project.rb
+++ b/app/models/trending_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class TrendingProject < ActiveRecord::Base
+class TrendingProject < ApplicationRecord
belongs_to :project
# The number of months to include in the trending calculation.
diff --git a/app/models/u2f_registration.rb b/app/models/u2f_registration.rb
index 37598173fd1..b4645462314 100644
--- a/app/models/u2f_registration.rb
+++ b/app/models/u2f_registration.rb
@@ -2,7 +2,7 @@
# Registration information for U2F (universal 2nd factor) devices, like Yubikeys
-class U2fRegistration < ActiveRecord::Base
+class U2fRegistration < ApplicationRecord
belongs_to :user
def self.register(user, app_id, params, challenges)
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 20860f14b83..9bffdcdb2e7 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class Upload < ActiveRecord::Base
+class Upload < ApplicationRecord
# Upper limit for foreground checksum processing
CHECKSUM_THRESHOLD = 100.megabytes
diff --git a/app/models/user.rb b/app/models/user.rb
index d2be26370ff..e0c518a9b75 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -432,7 +432,7 @@ class User < ApplicationRecord
fuzzy_arel_match(:name, query, lower_exact_match: true)
.or(fuzzy_arel_match(:username, query, lower_exact_match: true))
.or(arel_table[:email].eq(query))
- ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
+ ).reorder(order % { query: ApplicationRecord.connection.quote(query) }, :name)
end
# Limits the result set to users _not_ in the given query/list of IDs.
diff --git a/app/models/user_agent_detail.rb b/app/models/user_agent_detail.rb
index e2b2e7f1df9..fea1fce3c8d 100644
--- a/app/models/user_agent_detail.rb
+++ b/app/models/user_agent_detail.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserAgentDetail < ActiveRecord::Base
+class UserAgentDetail < ApplicationRecord
belongs_to :subject, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
validates :user_agent, :ip_address, :subject_id, :subject_type, presence: true
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
index 76e7bc06b4e..027ee44c6a9 100644
--- a/app/models/user_callout.rb
+++ b/app/models/user_callout.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserCallout < ActiveRecord::Base
+class UserCallout < ApplicationRecord
belongs_to :user
# We use `UserCalloutEnums.feature_names` here so that EE can more easily
diff --git a/app/models/user_custom_attribute.rb b/app/models/user_custom_attribute.rb
index e0ffe8ebbfd..727975c3f6e 100644
--- a/app/models/user_custom_attribute.rb
+++ b/app/models/user_custom_attribute.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserCustomAttribute < ActiveRecord::Base
+class UserCustomAttribute < ApplicationRecord
belongs_to :user
validates :user_id, :key, :value, presence: true
diff --git a/app/models/user_interacted_project.rb b/app/models/user_interacted_project.rb
index 5fc59b274f5..f6f72f4b77a 100644
--- a/app/models/user_interacted_project.rb
+++ b/app/models/user_interacted_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserInteractedProject < ActiveRecord::Base
+class UserInteractedProject < ApplicationRecord
belongs_to :user
belongs_to :project
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index 32d0407800f..282b192167f 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserPreference < ActiveRecord::Base
+class UserPreference < ApplicationRecord
# We could use enums, but Rails 4 doesn't support multiple
# enum options with same name for multiple fields, also it creates
# extra methods that aren't really needed here.
diff --git a/app/models/user_status.rb b/app/models/user_status.rb
index 2bbb0c59ac1..6ced4f56823 100644
--- a/app/models/user_status.rb
+++ b/app/models/user_status.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserStatus < ActiveRecord::Base
+class UserStatus < ApplicationRecord
include CacheMarkdownField
self.primary_key = :user_id
diff --git a/app/models/user_synced_attributes_metadata.rb b/app/models/user_synced_attributes_metadata.rb
index 7115262942d..5aacf11b1cb 100644
--- a/app/models/user_synced_attributes_metadata.rb
+++ b/app/models/user_synced_attributes_metadata.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UserSyncedAttributesMetadata < ActiveRecord::Base
+class UserSyncedAttributesMetadata < ApplicationRecord
belongs_to :user
validates :user, presence: true
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index bdaf58ae1c1..9be6bd2e6f3 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class UsersStarProject < ActiveRecord::Base
+class UsersStarProject < ApplicationRecord
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index e9f4e0b4b41..75825c8fac0 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -186,6 +186,7 @@ class ProjectPolicy < BasePolicy
rule { can?(:reporter_access) }.policy do
enable :download_code
+ enable :read_statistics
enable :download_wiki_code
enable :fork_project
enable :create_project_snippet
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 29656b17183..ed3daf6585b 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -4,6 +4,7 @@ module Ci
class BuildRunnerPresenter < SimpleDelegator
include Gitlab::Utils::StrongMemoize
+ DEFAULT_GIT_DEPTH_MERGE_REQUEST = 10
RUNNER_REMOTE_TAG_PREFIX = 'refs/tags/'.freeze
RUNNER_REMOTE_BRANCH_PREFIX = 'refs/remotes/origin/'.freeze
@@ -27,6 +28,7 @@ module Ci
def git_depth
strong_memoize(:git_depth) do
git_depth = variables&.find { |variable| variable[:key] == 'GIT_DEPTH' }&.dig(:value)
+ git_depth ||= DEFAULT_GIT_DEPTH_MERGE_REQUEST if merge_request_ref?
git_depth.to_i
end
end
@@ -35,8 +37,9 @@ module Ci
specs = []
if git_depth > 0
- specs << refspec_for_branch(ref) if branch? || merge_request_event?
+ specs << refspec_for_branch(ref) if branch? || legacy_detached_merge_request_pipeline?
specs << refspec_for_tag(ref) if tag?
+ specs << refspec_for_merge_request_ref if merge_request_ref?
else
specs << refspec_for_branch
specs << refspec_for_tag
@@ -83,5 +86,9 @@ module Ci
def refspec_for_tag(ref = '*')
"+#{Gitlab::Git::TAG_REF_PREFIX}#{ref}:#{RUNNER_REMOTE_TAG_PREFIX}#{ref}"
end
+
+ def refspec_for_merge_request_ref
+ "+#{ref}:#{ref}"
+ end
end
end
diff --git a/app/presenters/clusters/cluster_presenter.rb b/app/presenters/clusters/cluster_presenter.rb
index 7a5b68f9a4b..81994bbce7d 100644
--- a/app/presenters/clusters/cluster_presenter.rb
+++ b/app/presenters/clusters/cluster_presenter.rb
@@ -48,6 +48,10 @@ module Clusters
end
end
+ def read_only_kubernetes_platform_fields?
+ !cluster.provided_by_user?
+ end
+
private
def clusterable
diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb
index 066e30cd3bb..d3d5883e46b 100644
--- a/app/serializers/diff_file_entity.rb
+++ b/app/serializers/diff_file_entity.rb
@@ -57,7 +57,7 @@ class DiffFileEntity < DiffFileBaseEntity
diff_file.diff_lines_for_serializer
end
- expose :is_fully_expanded, if: -> (diff_file, _) { Feature.enabled?(:expand_diff_full_file, default_enabled: true) && diff_file.text? } do |diff_file|
+ expose :is_fully_expanded, if: -> (diff_file, _) { diff_file.text? } do |diff_file|
diff_file.fully_expanded?
end
diff --git a/app/services/after_branch_delete_service.rb b/app/services/after_branch_delete_service.rb
index e7eb74d3e7d..ece9fbbef43 100644
--- a/app/services/after_branch_delete_service.rb
+++ b/app/services/after_branch_delete_service.rb
@@ -1,9 +1,6 @@
# frozen_string_literal: true
-##
-# Branch can be deleted either by DeleteBranchService
-# or by GitPushService.
-#
+# Branch can be deleted either by DeleteBranchService or by Git::BranchPushService.
class AfterBranchDeleteService < BaseService
attr_reader :branch_name
diff --git a/app/services/ci/destroy_pipeline_service.rb b/app/services/ci/destroy_pipeline_service.rb
index 5c4a34043c1..2292ec42b16 100644
--- a/app/services/ci/destroy_pipeline_service.rb
+++ b/app/services/ci/destroy_pipeline_service.rb
@@ -6,6 +6,8 @@ module Ci
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_pipeline, pipeline)
pipeline.destroy!
+
+ Gitlab::Cache::Ci::ProjectPipelineStatus.new(pipeline.project).delete_from_cache
end
end
end
diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb
index cbd1cf03ae1..14a45437287 100644
--- a/app/services/clusters/applications/base_service.rb
+++ b/app/services/clusters/applications/base_service.rb
@@ -41,7 +41,7 @@ module Clusters
raise NotImplementedError
end
- def builders
+ def builder
raise NotImplementedError
end
@@ -50,11 +50,27 @@ module Clusters
end
def instantiate_application
- builder.call(@cluster) || raise(InvalidApplicationError, "invalid application: #{application_name}")
+ raise_invalid_application_error if invalid_application?
+
+ builder || raise(InvalidApplicationError, "invalid application: #{application_name}")
end
- def builder
- builders[application_name] || raise(InvalidApplicationError, "invalid application: #{application_name}")
+ def raise_invalid_application_error
+ raise(InvalidApplicationError, "invalid application: #{application_name}")
+ end
+
+ def invalid_application?
+ unknown_application? || (!cluster.project_type? && project_only_application?)
+ end
+
+ def unknown_application?
+ Clusters::Cluster::APPLICATIONS.keys.exclude?(application_name)
+ end
+
+ # These applications will need extra configuration to enable them to work
+ # with groups of projects
+ def project_only_application?
+ Clusters::Cluster::PROJECT_ONLY_APPLICATIONS.include?(application_name)
end
def application_name
diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb
index bd7c31bb981..ae36da7b3dd 100644
--- a/app/services/clusters/applications/create_service.rb
+++ b/app/services/clusters/applications/create_service.rb
@@ -9,25 +9,9 @@ module Clusters
application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker
end
- def builders
- {
- "helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm },
- "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress },
- "cert_manager" => -> (cluster) { cluster.application_cert_manager || cluster.build_application_cert_manager }
- }.tap do |hash|
- hash.merge!(project_builders) if cluster.project_type?
- end
- end
-
- # These applications will need extra configuration to enable them to work
- # with groups of projects
- def project_builders
- {
- "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus },
- "runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner },
- "jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter },
- "knative" => -> (cluster) { cluster.application_knative || cluster.build_application_knative }
- }
+ def builder
+ cluster.method("application_#{application_name}").call ||
+ cluster.method("build_application_#{application_name}").call
end
end
end
diff --git a/app/services/clusters/applications/update_service.rb b/app/services/clusters/applications/update_service.rb
index a9d4e609992..5071c31839c 100644
--- a/app/services/clusters/applications/update_service.rb
+++ b/app/services/clusters/applications/update_service.rb
@@ -9,25 +9,8 @@ module Clusters
ClusterPatchAppWorker
end
- def builders
- {
- "helm" => -> (cluster) { cluster.application_helm },
- "ingress" => -> (cluster) { cluster.application_ingress },
- "cert_manager" => -> (cluster) { cluster.application_cert_manager }
- }.tap do |hash|
- hash.merge!(project_builders) if cluster.project_type?
- end
- end
-
- # These applications will need extra configuration to enable them to work
- # with groups of projects
- def project_builders
- {
- "prometheus" => -> (cluster) { cluster.application_prometheus },
- "runner" => -> (cluster) { cluster.application_runner },
- "jupyter" => -> (cluster) { cluster.application_jupyter },
- "knative" => -> (cluster) { cluster.application_knative }
- }
+ def builder
+ cluster.method("application_#{application_name}").call
end
end
end
diff --git a/app/services/concerns/suggestible.rb b/app/services/concerns/suggestible.rb
new file mode 100644
index 00000000000..0b9822b1909
--- /dev/null
+++ b/app/services/concerns/suggestible.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Suggestible
+ extend ActiveSupport::Concern
+
+ # This translates into limiting suggestion changes to `suggestion:-100+100`.
+ MAX_LINES_CONTEXT = 100.freeze
+
+ def fetch_from_content
+ diff_file.new_blob_lines_between(from_line, to_line).join
+ end
+
+ def from_line
+ real_above = [lines_above, MAX_LINES_CONTEXT].min
+ [target_line - real_above, 1].max
+ end
+
+ def to_line
+ real_below = [lines_below, MAX_LINES_CONTEXT].min
+ target_line + real_below
+ end
+
+ def diff_file
+ raise NotImplementedError
+ end
+
+ def target_line
+ raise NotImplementedError
+ end
+end
diff --git a/app/services/concerns/users/participable_service.rb b/app/services/concerns/users/participable_service.rb
index 6713b6617ae..a3cc6014fd3 100644
--- a/app/services/concerns/users/participable_service.rb
+++ b/app/services/concerns/users/participable_service.rb
@@ -28,19 +28,35 @@ module Users
end
def groups
- current_user.authorized_groups.sort_by(&:path).map do |group|
- group_as_hash(group)
+ group_counts = GroupMember
+ .in_groups(current_user.authorized_groups)
+ .non_request
+ .count_users_by_group_id
+
+ current_user.authorized_groups.with_route.sort_by(&:path).map do |group|
+ group_as_hash(group, group_counts)
end
end
private
def user_as_hash(user)
- { type: user.class.name, username: user.username, name: user.name, avatar_url: user.avatar_url }
+ {
+ type: user.class.name,
+ username: user.username,
+ name: user.name,
+ avatar_url: user.avatar_url
+ }
end
- def group_as_hash(group)
- { type: group.class.name, username: group.full_path, name: group.full_name, avatar_url: group.avatar_url, count: group.users.count }
+ def group_as_hash(group, group_counts)
+ {
+ type: group.class.name,
+ username: group.full_path,
+ name: group.full_name,
+ avatar_url: group.avatar_url,
+ count: group_counts.fetch(group.id, 0)
+ }
end
end
end
diff --git a/app/services/error_tracking/list_issues_service.rb b/app/services/error_tracking/list_issues_service.rb
index a6c6bec9598..86ab21fa865 100644
--- a/app/services/error_tracking/list_issues_service.rb
+++ b/app/services/error_tracking/list_issues_service.rb
@@ -18,7 +18,7 @@ module ErrorTracking
end
if result[:error].present?
- return error(result[:error], :bad_request)
+ return error(result[:error], http_status_from_error_type(result[:error_type]))
end
success(issues: result[:issues])
@@ -30,6 +30,15 @@ module ErrorTracking
private
+ def http_status_from_error_type(error_type)
+ case error_type
+ when ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS
+ :internal_server_error
+ else
+ :bad_request
+ end
+ end
+
def project_error_tracking_setting
project.error_tracking_setting
end
diff --git a/app/services/git/branch_push_service.rb b/app/services/git/branch_push_service.rb
new file mode 100644
index 00000000000..b55aeb5f2b9
--- /dev/null
+++ b/app/services/git/branch_push_service.rb
@@ -0,0 +1,244 @@
+# frozen_string_literal: true
+
+module Git
+ class BranchPushService < BaseService
+ attr_accessor :push_data, :push_commits
+ include Gitlab::Access
+ include Gitlab::Utils::StrongMemoize
+
+ # The N most recent commits to process in a single push payload.
+ PROCESS_COMMIT_LIMIT = 100
+
+ # This method will be called after each git update
+ # and only if the provided user and project are present in GitLab.
+ #
+ # All callbacks for post receive action should be placed here.
+ #
+ # Next, this method:
+ # 1. Creates the push event
+ # 2. Updates merge requests
+ # 3. Recognizes cross-references from commit messages
+ # 4. Executes the project's webhooks
+ # 5. Executes the project's services
+ # 6. Checks if the project's main language has changed
+ #
+ def execute
+ update_commits
+ execute_related_hooks
+ perform_housekeeping
+
+ update_remote_mirrors
+ update_caches
+
+ update_signatures
+ end
+
+ def update_commits
+ project.repository.after_create if project.empty_repo?
+ project.repository.after_push_commit(branch_name)
+
+ if push_remove_branch?
+ project.repository.after_remove_branch
+ @push_commits = []
+ elsif push_to_new_branch?
+ project.repository.after_create_branch
+
+ # Re-find the pushed commits.
+ if default_branch?
+ # Initial push to the default branch. Take the full history of that branch as "newly pushed".
+ process_default_branch
+ else
+ # Use the pushed commits that aren't reachable by the default branch
+ # as a heuristic. This may include more commits than are actually pushed, but
+ # that shouldn't matter because we check for existing cross-references later.
+ @push_commits = project.repository.commits_between(project.default_branch, params[:newrev])
+
+ # don't process commits for the initial push to the default branch
+ process_commit_messages
+ end
+ elsif push_to_existing_branch?
+ # Collect data for this git push
+ @push_commits = project.repository.commits_between(params[:oldrev], params[:newrev])
+
+ process_commit_messages
+
+ # Update the bare repositories info/attributes file using the contents of the default branches
+ # .gitattributes file
+ update_gitattributes if default_branch?
+ end
+ end
+
+ def update_gitattributes
+ project.repository.copy_gitattributes(params[:ref])
+ end
+
+ def update_caches
+ if default_branch?
+ if push_to_new_branch?
+ # If this is the initial push into the default branch, the file type caches
+ # will already be reset as a result of `Project#change_head`.
+ types = []
+ else
+ paths = Set.new
+
+ last_pushed_commits.each do |commit|
+ commit.raw_deltas.each do |diff|
+ paths << diff.new_path
+ end
+ end
+
+ types = Gitlab::FileDetector.types_in_paths(paths.to_a)
+ end
+
+ DetectRepositoryLanguagesWorker.perform_async(@project.id, current_user.id)
+ else
+ types = []
+ end
+
+ ProjectCacheWorker.perform_async(project.id, types, [:commit_count, :repository_size])
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def update_signatures
+ commit_shas = last_pushed_commits.map(&:sha)
+
+ return if commit_shas.empty?
+
+ shas_with_cached_signatures = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha)
+ commit_shas -= shas_with_cached_signatures
+
+ return if commit_shas.empty?
+
+ commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas)
+
+ CreateGpgSignatureWorker.perform_async(commit_shas, project.id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # Schedules processing of commit messages.
+ def process_commit_messages
+ default = default_branch?
+
+ last_pushed_commits.each do |commit|
+ if commit.matches_cross_reference_regex?
+ ProcessCommitWorker
+ .perform_async(project.id, current_user.id, commit.to_hash, default)
+ end
+ end
+ end
+
+ def update_remote_mirrors
+ return unless project.has_remote_mirror?
+
+ project.mark_stuck_remote_mirrors_as_failed!
+ project.update_remote_mirrors
+ end
+
+ def execute_related_hooks
+ # Update merge requests that may be affected by this push. A new branch
+ # could cause the last commit of a merge request to change.
+ #
+ UpdateMergeRequestsWorker
+ .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
+
+ EventCreateService.new.push(project, current_user, build_push_data)
+ Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, pipeline_options)
+
+ project.execute_hooks(build_push_data.dup, :push_hooks)
+ project.execute_services(build_push_data.dup, :push_hooks)
+
+ if push_remove_branch?
+ AfterBranchDeleteService
+ .new(project, current_user)
+ .execute(branch_name)
+ end
+ end
+
+ def perform_housekeeping
+ housekeeping = Projects::HousekeepingService.new(project)
+ housekeeping.increment!
+ housekeeping.execute if housekeeping.needed?
+ rescue Projects::HousekeepingService::LeaseTaken
+ end
+
+ def process_default_branch
+ offset = [push_commits_count_for_ref - PROCESS_COMMIT_LIMIT, 0].max
+ @push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
+
+ project.after_create_default_branch
+ end
+
+ def build_push_data
+ @push_data ||= Gitlab::DataBuilder::Push.build(
+ project,
+ current_user,
+ params[:oldrev],
+ params[:newrev],
+ params[:ref],
+ @push_commits,
+ commits_count: commits_count,
+ push_options: params[:push_options] || []
+ )
+ end
+
+ def push_to_existing_branch?
+ # Return if this is not a push to a branch (e.g. new commits)
+ branch_ref? && !Gitlab::Git.blank_ref?(params[:oldrev])
+ end
+
+ def push_to_new_branch?
+ strong_memoize(:push_to_new_branch) do
+ branch_ref? && Gitlab::Git.blank_ref?(params[:oldrev])
+ end
+ end
+
+ def push_remove_branch?
+ strong_memoize(:push_remove_branch) do
+ branch_ref? && Gitlab::Git.blank_ref?(params[:newrev])
+ end
+ end
+
+ def default_branch?
+ branch_ref? &&
+ (branch_name == project.default_branch || project.default_branch.nil?)
+ end
+
+ def commit_user(commit)
+ commit.author || current_user
+ end
+
+ def branch_name
+ strong_memoize(:branch_name) do
+ Gitlab::Git.ref_name(params[:ref])
+ end
+ end
+
+ def branch_ref?
+ strong_memoize(:branch_ref) do
+ Gitlab::Git.branch_ref?(params[:ref])
+ end
+ end
+
+ def commits_count
+ return push_commits_count_for_ref if default_branch? && push_to_new_branch?
+
+ Array(@push_commits).size
+ end
+
+ def push_commits_count_for_ref
+ strong_memoize(:push_commits_count_for_ref) do
+ project.repository.commit_count_for_ref(params[:ref])
+ end
+ end
+
+ def last_pushed_commits
+ @last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT)
+ end
+
+ private
+
+ def pipeline_options
+ {} # to be overridden in EE
+ end
+ end
+end
diff --git a/app/services/git/tag_push_service.rb b/app/services/git/tag_push_service.rb
new file mode 100644
index 00000000000..318dfd4f886
--- /dev/null
+++ b/app/services/git/tag_push_service.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Git
+ class TagPushService < BaseService
+ attr_accessor :push_data
+
+ def execute
+ project.repository.after_create if project.empty_repo?
+ project.repository.before_push_tag
+
+ @push_data = build_push_data
+
+ EventCreateService.new.push(project, current_user, push_data)
+ Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push, pipeline_options)
+
+ SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks)
+ project.execute_hooks(push_data.dup, :tag_push_hooks)
+ project.execute_services(push_data.dup, :tag_push_hooks)
+
+ ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
+
+ true
+ end
+
+ private
+
+ def build_push_data
+ commits = []
+ message = nil
+
+ unless Gitlab::Git.blank_ref?(params[:newrev])
+ tag_name = Gitlab::Git.ref_name(params[:ref])
+ tag = project.repository.find_tag(tag_name)
+
+ if tag && tag.target == params[:newrev]
+ commit = project.commit(tag.dereferenced_target)
+ commits = [commit].compact
+ message = tag.message
+ end
+ end
+
+ Gitlab::DataBuilder::Push.build(
+ project,
+ current_user,
+ params[:oldrev],
+ params[:newrev],
+ params[:ref],
+ commits,
+ message,
+ push_options: params[:push_options] || [])
+ end
+
+ def build_system_push_data
+ Gitlab::DataBuilder::Push.build(
+ project,
+ current_user,
+ params[:oldrev],
+ params[:newrev],
+ params[:ref],
+ [],
+ '')
+ end
+
+ def pipeline_options
+ {} # to be overridden in EE
+ end
+ end
+end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
deleted file mode 100644
index f387c749a21..00000000000
--- a/app/services/git_push_service.rb
+++ /dev/null
@@ -1,240 +0,0 @@
-# frozen_string_literal: true
-
-class GitPushService < BaseService
- attr_accessor :push_data, :push_commits
- include Gitlab::Access
- include Gitlab::Utils::StrongMemoize
-
- # The N most recent commits to process in a single push payload.
- PROCESS_COMMIT_LIMIT = 100
-
- # This method will be called after each git update
- # and only if the provided user and project are present in GitLab.
- #
- # All callbacks for post receive action should be placed here.
- #
- # Next, this method:
- # 1. Creates the push event
- # 2. Updates merge requests
- # 3. Recognizes cross-references from commit messages
- # 4. Executes the project's webhooks
- # 5. Executes the project's services
- # 6. Checks if the project's main language has changed
- #
- def execute
- project.repository.after_create if project.empty_repo?
- project.repository.after_push_commit(branch_name)
-
- if push_remove_branch?
- project.repository.after_remove_branch
- @push_commits = []
- elsif push_to_new_branch?
- project.repository.after_create_branch
-
- # Re-find the pushed commits.
- if default_branch?
- # Initial push to the default branch. Take the full history of that branch as "newly pushed".
- process_default_branch
- else
- # Use the pushed commits that aren't reachable by the default branch
- # as a heuristic. This may include more commits than are actually pushed, but
- # that shouldn't matter because we check for existing cross-references later.
- @push_commits = project.repository.commits_between(project.default_branch, params[:newrev])
-
- # don't process commits for the initial push to the default branch
- process_commit_messages
- end
- elsif push_to_existing_branch?
- # Collect data for this git push
- @push_commits = project.repository.commits_between(params[:oldrev], params[:newrev])
-
- process_commit_messages
-
- # Update the bare repositories info/attributes file using the contents of the default branches
- # .gitattributes file
- update_gitattributes if default_branch?
- end
-
- execute_related_hooks
- perform_housekeeping
-
- update_remote_mirrors
- update_caches
-
- update_signatures
- end
-
- def update_gitattributes
- project.repository.copy_gitattributes(params[:ref])
- end
-
- def update_caches
- if default_branch?
- if push_to_new_branch?
- # If this is the initial push into the default branch, the file type caches
- # will already be reset as a result of `Project#change_head`.
- types = []
- else
- paths = Set.new
-
- last_pushed_commits.each do |commit|
- commit.raw_deltas.each do |diff|
- paths << diff.new_path
- end
- end
-
- types = Gitlab::FileDetector.types_in_paths(paths.to_a)
- end
-
- DetectRepositoryLanguagesWorker.perform_async(@project.id, current_user.id)
- else
- types = []
- end
-
- ProjectCacheWorker.perform_async(project.id, types, [:commit_count, :repository_size])
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def update_signatures
- commit_shas = last_pushed_commits.map(&:sha)
-
- return if commit_shas.empty?
-
- shas_with_cached_signatures = GpgSignature.where(commit_sha: commit_shas).pluck(:commit_sha)
- commit_shas -= shas_with_cached_signatures
-
- return if commit_shas.empty?
-
- commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas)
-
- CreateGpgSignatureWorker.perform_async(commit_shas, project.id)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # Schedules processing of commit messages.
- def process_commit_messages
- default = default_branch?
-
- last_pushed_commits.each do |commit|
- if commit.matches_cross_reference_regex?
- ProcessCommitWorker
- .perform_async(project.id, current_user.id, commit.to_hash, default)
- end
- end
- end
-
- protected
-
- def update_remote_mirrors
- return unless project.has_remote_mirror?
-
- project.mark_stuck_remote_mirrors_as_failed!
- project.update_remote_mirrors
- end
-
- def execute_related_hooks
- # Update merge requests that may be affected by this push. A new branch
- # could cause the last commit of a merge request to change.
- #
- UpdateMergeRequestsWorker
- .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
-
- EventCreateService.new.push(project, current_user, build_push_data)
- Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push, pipeline_options)
-
- project.execute_hooks(build_push_data.dup, :push_hooks)
- project.execute_services(build_push_data.dup, :push_hooks)
-
- if push_remove_branch?
- AfterBranchDeleteService
- .new(project, current_user)
- .execute(branch_name)
- end
- end
-
- def perform_housekeeping
- housekeeping = Projects::HousekeepingService.new(project)
- housekeeping.increment!
- housekeeping.execute if housekeeping.needed?
- rescue Projects::HousekeepingService::LeaseTaken
- end
-
- def process_default_branch
- offset = [push_commits_count_for_ref - PROCESS_COMMIT_LIMIT, 0].max
- @push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
-
- project.after_create_default_branch
- end
-
- def build_push_data
- @push_data ||= Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- @push_commits,
- commits_count: commits_count,
- push_options: params[:push_options] || [])
- end
-
- def push_to_existing_branch?
- # Return if this is not a push to a branch (e.g. new commits)
- branch_ref? && !Gitlab::Git.blank_ref?(params[:oldrev])
- end
-
- def push_to_new_branch?
- strong_memoize(:push_to_new_branch) do
- branch_ref? && Gitlab::Git.blank_ref?(params[:oldrev])
- end
- end
-
- def push_remove_branch?
- strong_memoize(:push_remove_branch) do
- branch_ref? && Gitlab::Git.blank_ref?(params[:newrev])
- end
- end
-
- def default_branch?
- branch_ref? &&
- (branch_name == project.default_branch || project.default_branch.nil?)
- end
-
- def commit_user(commit)
- commit.author || current_user
- end
-
- def branch_name
- strong_memoize(:branch_name) do
- Gitlab::Git.ref_name(params[:ref])
- end
- end
-
- def branch_ref?
- strong_memoize(:branch_ref) do
- Gitlab::Git.branch_ref?(params[:ref])
- end
- end
-
- def commits_count
- return push_commits_count_for_ref if default_branch? && push_to_new_branch?
-
- Array(@push_commits).size
- end
-
- def push_commits_count_for_ref
- strong_memoize(:push_commits_count_for_ref) do
- project.repository.commit_count_for_ref(params[:ref])
- end
- end
-
- def last_pushed_commits
- @last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT)
- end
-
- private
-
- def pipeline_options
- {} # to be overridden in EE
- end
-end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
deleted file mode 100644
index e39b3603c6c..00000000000
--- a/app/services/git_tag_push_service.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-# frozen_string_literal: true
-
-class GitTagPushService < BaseService
- attr_accessor :push_data
-
- def execute
- project.repository.after_create if project.empty_repo?
- project.repository.before_push_tag
-
- @push_data = build_push_data
-
- EventCreateService.new.push(project, current_user, push_data)
- Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push, pipeline_options)
-
- SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks)
- project.execute_hooks(push_data.dup, :tag_push_hooks)
- project.execute_services(push_data.dup, :tag_push_hooks)
-
- ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
-
- true
- end
-
- private
-
- def build_push_data
- commits = []
- message = nil
-
- unless Gitlab::Git.blank_ref?(params[:newrev])
- tag_name = Gitlab::Git.ref_name(params[:ref])
- tag = project.repository.find_tag(tag_name)
-
- if tag && tag.target == params[:newrev]
- commit = project.commit(tag.dereferenced_target)
- commits = [commit].compact
- message = tag.message
- end
- end
-
- Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- commits,
- message,
- push_options: params[:push_options] || [])
- end
-
- def build_system_push_data
- Gitlab::DataBuilder::Push.build(
- project,
- current_user,
- params[:oldrev],
- params[:newrev],
- params[:ref],
- [],
- '')
- end
-
- def pipeline_options
- {} # to be overridden in EE
- end
-end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 49739083868..04dfcfbc22d 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -80,13 +80,11 @@ class IssuableBaseService < BaseService
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def filter_labels_in_param(key)
return if params[key].to_a.empty?
- params[key] = available_labels.where(id: params[key]).pluck(:id)
+ params[key] = available_labels.id_in(params[key]).pluck_primary_key
end
- # rubocop: enable CodeReuse/ActiveRecord
def find_or_create_label_ids
labels = params.delete(:labels)
@@ -123,7 +121,7 @@ class IssuableBaseService < BaseService
new_label_ids -= remove_label_ids if remove_label_ids
end
- new_label_ids
+ new_label_ids.uniq
end
def handle_quick_actions_on_create(issuable)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 11ede5223e5..3e208241da5 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -54,7 +54,7 @@ module MergeRequests
merge_request, merge_request.project, current_user, merge_request.assignee)
end
- def create_merge_request_pipeline(merge_request, user)
+ def create_pipeline_for(merge_request, user)
return unless Feature.enabled?(:ci_merge_request_pipeline,
merge_request.source_project,
default_enabled: true)
@@ -65,12 +65,24 @@ module MergeRequests
return if merge_request.merge_request_pipeline_exists?
return if merge_request.has_no_commits?
- Ci::CreatePipelineService
- .new(merge_request.source_project, user, ref: merge_request.source_branch)
- .execute(:merge_request_event,
- ignore_skip_ci: true,
- save_on_errors: false,
- merge_request: merge_request)
+ create_detached_merge_request_pipeline(merge_request, user)
+ end
+
+ def create_detached_merge_request_pipeline(merge_request, user)
+ if can_use_merge_request_ref?(merge_request)
+ Ci::CreatePipelineService.new(merge_request.source_project, user,
+ ref: merge_request.ref_path)
+ .execute(:merge_request_event, merge_request: merge_request)
+ else
+ Ci::CreatePipelineService.new(merge_request.source_project, user,
+ ref: merge_request.source_branch)
+ .execute(:merge_request_event, merge_request: merge_request)
+ end
+ end
+
+ def can_use_merge_request_ref?(merge_request)
+ Feature.enabled?(:ci_use_merge_request_ref, project, default_enabled: true) &&
+ !merge_request.for_fork?
end
# Returns all origin and fork merge requests from `@project` satisfying passed arguments.
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index 02c2388c05c..06e46595b95 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -25,7 +25,7 @@ module MergeRequests
def after_create(issuable)
todo_service.new_merge_request(issuable, current_user)
issuable.cache_merge_request_closes_issues!(current_user)
- create_merge_request_pipeline(issuable, current_user)
+ create_pipeline_for(issuable, current_user)
issuable.update_head_pipeline
super
diff --git a/app/services/merge_requests/delete_non_latest_diffs_service.rb b/app/services/merge_requests/delete_non_latest_diffs_service.rb
index d5929446122..bdb7ec8a7c2 100644
--- a/app/services/merge_requests/delete_non_latest_diffs_service.rb
+++ b/app/services/merge_requests/delete_non_latest_diffs_service.rb
@@ -8,15 +8,13 @@ module MergeRequests
@merge_request = merge_request
end
- # rubocop: disable CodeReuse/ActiveRecord
def execute
diffs = @merge_request.non_latest_diffs.with_files
diffs.each_batch(of: BATCH_SIZE) do |relation, index|
- ids = relation.pluck(:id).map { |id| [id] }
+ ids = relation.pluck_primary_key.map { |id| [id] }
DeleteDiffFilesWorker.bulk_perform_in(index * 5.minutes, ids)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 8241e408ce5..d8a78001b79 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -76,8 +76,8 @@ module MergeRequests
def try_merge
repository.merge(current_user, source, merge_request, commit_message)
rescue Gitlab::Git::PreReceiveError => e
- handle_merge_error(log_message: e.message)
- raise_error('Something went wrong during merge pre-receive hook')
+ raise MergeError,
+ "Something went wrong during merge pre-receive hook. #{e.message}".strip
rescue => e
handle_merge_error(log_message: e.message)
raise_error('Something went wrong during merge')
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
index 69cc441f1bb..87147d90c32 100644
--- a/app/services/merge_requests/merge_to_ref_service.rb
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -41,9 +41,7 @@ module MergeRequests
super
error =
- if Feature.disabled?(:merge_to_tmp_merge_ref_path, project)
- 'Feature is not enabled'
- elsif !hooks_validation_pass?(merge_request)
+ if !hooks_validation_pass?(merge_request)
hooks_validation_error(merge_request)
elsif !@merge_request.mergeable_to_ref?
"Merge request is not mergeable to #{target_ref}"
diff --git a/app/services/merge_requests/migrate_external_diffs_service.rb b/app/services/merge_requests/migrate_external_diffs_service.rb
new file mode 100644
index 00000000000..16050244637
--- /dev/null
+++ b/app/services/merge_requests/migrate_external_diffs_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MigrateExternalDiffsService < ::BaseService
+ MAX_JOBS = 1000.freeze
+
+ attr_reader :diff
+
+ def self.enqueue!
+ ids = MergeRequestDiff.ids_for_external_storage_migration(limit: MAX_JOBS)
+
+ MigrateExternalDiffsWorker.bulk_perform_async(ids.map { |id| [id] })
+ end
+
+ def initialize(merge_request_diff)
+ @diff = merge_request_diff
+ end
+
+ def execute
+ diff.migrate_files_to_external_storage!
+ end
+ end
+end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index f712b8863cd..51d27673787 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -20,6 +20,7 @@ module MergeRequests
close_upon_missing_source_branch_ref
post_merge_manually_merged
reload_merge_requests
+ outdate_suggestions
reset_merge_when_pipeline_succeeds
mark_pending_todos_done
cache_merge_requests_closing_issues
@@ -106,7 +107,7 @@ module MergeRequests
end
merge_request.mark_as_unchecked
- create_merge_request_pipeline(merge_request, current_user)
+ create_pipeline_for(merge_request, current_user)
UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
end
@@ -125,6 +126,14 @@ module MergeRequests
merge_request.source_branch == @push.branch_name
end
+ def outdate_suggestions
+ outdate_service = Suggestions::OutdateService.new
+
+ merge_requests_for_source_branch.each do |merge_request|
+ outdate_service.execute(merge_request)
+ end
+ end
+
def reset_merge_when_pipeline_succeeds
merge_requests_for_source_branch.each(&:reset_merge_when_pipeline_succeeds)
end
diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb
index cbe5996e8ca..f6c04a7bae2 100644
--- a/app/services/milestones/promote_service.rb
+++ b/app/services/milestones/promote_service.rb
@@ -26,17 +26,15 @@ module Milestones
private
- # rubocop: disable CodeReuse/ActiveRecord
def milestone_ids_for_merge(group_milestone)
# Pluck need to be used here instead of select so the array of ids
# is persistent after old milestones gets deleted.
@milestone_ids_for_merge ||= begin
search_params = { title: group_milestone.title, project_ids: group_project_ids, state: 'all' }
milestones = MilestonesFinder.new(search_params).execute
- milestones.pluck(:id)
+ milestones.pluck_primary_key
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def move_children_to_group_milestone(group_milestone)
milestone_ids_for_merge(group_milestone).in_groups_of(100, false) do |milestone_ids|
diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb
index bbdde4408d2..afb9048e87b 100644
--- a/app/services/projects/after_import_service.rb
+++ b/app/services/projects/after_import_service.rb
@@ -9,7 +9,7 @@ module Projects
end
def execute
- Projects::HousekeepingService.new(@project).execute do
+ Projects::HousekeepingService.new(@project, :gc).execute do
repository.delete_all_refs_except(RESERVED_REF_PREFIXES)
end
rescue Projects::HousekeepingService::LeaseTaken => e
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
index 2f6dc4207dd..10bd5363b51 100644
--- a/app/services/projects/housekeeping_service.rb
+++ b/app/services/projects/housekeeping_service.rb
@@ -18,8 +18,9 @@ module Projects
end
end
- def initialize(project)
+ def initialize(project, task = nil)
@project = project
+ @task = task
end
def execute
@@ -69,6 +70,8 @@ module Projects
end
def task
+ return @task if @task
+
if pushes_since_gc % gc_period == 0
:gc
elsif pushes_since_gc % full_repack_period == 0
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 131efb8925e..f463e08ee7e 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -4,21 +4,24 @@ module QuickActions
class InterpretService < BaseService
include Gitlab::Utils::StrongMemoize
include Gitlab::QuickActions::Dsl
+ include Gitlab::QuickActions::IssueActions
+ include Gitlab::QuickActions::IssueAndMergeRequestActions
+ include Gitlab::QuickActions::IssuableActions
+ include Gitlab::QuickActions::MergeRequestActions
+ include Gitlab::QuickActions::CommitActions
+ include Gitlab::QuickActions::CommonActions
- attr_reader :issuable
+ attr_reader :quick_action_target
# Counts how many commands have been executed.
# Used to display relevant feedback on UI when a note
# with only commands has been processed.
attr_accessor :commands_executed_count
- SHRUG = '¯\\_(ツ)_/¯'.freeze
- TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
-
- # Takes an issuable and returns an array of all the available commands
+ # Takes an quick_action_target and returns an array of all the available commands
# represented with .to_h
- def available_commands(issuable)
- @issuable = issuable
+ def available_commands(quick_action_target)
+ @quick_action_target = quick_action_target
self.class.command_definitions.map do |definition|
next unless definition.available?(self)
@@ -29,10 +32,10 @@ module QuickActions
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
- def execute(content, issuable, only: nil)
+ def execute(content, quick_action_target, only: nil)
return [content, {}] unless current_user.can?(:use_quick_actions)
- @issuable = issuable
+ @quick_action_target = quick_action_target
@updates = {}
content, commands = extractor.extract_commands(content, only: only)
@@ -43,10 +46,10 @@ module QuickActions
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and array of changes explained.
- def explain(content, issuable)
+ def explain(content, quick_action_target)
return [content, []] unless current_user.can?(:use_quick_actions)
- @issuable = issuable
+ @quick_action_target = quick_action_target
content, commands = extractor.extract_commands(content)
commands = explain_commands(commands)
@@ -59,598 +62,6 @@ module QuickActions
Gitlab::QuickActions::Extractor.new(self.class.command_definitions)
end
- desc do
- "Close this #{issuable.to_ability_name.humanize(capitalize: false)}"
- end
- explanation do
- "Closes this #{issuable.to_ability_name.humanize(capitalize: false)}."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.open? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- command :close do
- @updates[:state_event] = 'close'
- end
-
- desc do
- "Reopen this #{issuable.to_ability_name.humanize(capitalize: false)}"
- end
- explanation do
- "Reopens this #{issuable.to_ability_name.humanize(capitalize: false)}."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.closed? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- command :reopen do
- @updates[:state_event] = 'reopen'
- end
-
- desc 'Merge (when the pipeline succeeds)'
- explanation 'Merges this merge request when the pipeline succeeds.'
- condition do
- last_diff_sha = params && params[:merge_request_diff_head_sha]
- issuable.is_a?(MergeRequest) &&
- issuable.persisted? &&
- issuable.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
- end
- command :merge do
- @updates[:merge] = params[:merge_request_diff_head_sha]
- end
-
- desc 'Change title'
- explanation do |title_param|
- "Changes the title to \"#{title_param}\"."
- end
- params '<New title>'
- condition do
- issuable.persisted? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- command :title do |title_param|
- @updates[:title] = title_param
- end
-
- desc 'Assign'
- # rubocop: disable CodeReuse/ActiveRecord
- explanation do |users|
- users = issuable.allows_multiple_assignees? ? users : users.take(1)
- "Assigns #{users.map(&:to_reference).to_sentence}."
- end
- # rubocop: enable CodeReuse/ActiveRecord
- params do
- issuable.allows_multiple_assignees? ? '@user1 @user2' : '@user'
- end
- condition do
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |assignee_param|
- extract_users(assignee_param)
- end
- command :assign do |users|
- next if users.empty?
-
- if issuable.allows_multiple_assignees?
- @updates[:assignee_ids] ||= issuable.assignees.map(&:id)
- @updates[:assignee_ids] += users.map(&:id)
- else
- @updates[:assignee_ids] = [users.first.id]
- end
- end
-
- desc do
- if issuable.allows_multiple_assignees?
- 'Remove all or specific assignee(s)'
- else
- 'Remove assignee'
- end
- end
- explanation do |users = nil|
- assignees = issuable.assignees
- assignees &= users if users.present? && issuable.allows_multiple_assignees?
- "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
- end
- params do
- issuable.allows_multiple_assignees? ? '@user1 @user2' : ''
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.assignees.any? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |unassign_param|
- # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
- extract_users(unassign_param) if issuable.allows_multiple_assignees?
- end
- command :unassign do |users = nil|
- if issuable.allows_multiple_assignees? && users&.any?
- @updates[:assignee_ids] ||= issuable.assignees.map(&:id)
- @updates[:assignee_ids] -= users.map(&:id)
- else
- @updates[:assignee_ids] = []
- end
- end
-
- desc 'Set milestone'
- explanation do |milestone|
- "Sets the milestone to #{milestone.to_reference}." if milestone
- end
- params '%"milestone"'
- condition do
- current_user.can?(:"admin_#{issuable.to_ability_name}", project) &&
- find_milestones(project, state: 'active').any?
- end
- parse_params do |milestone_param|
- extract_references(milestone_param, :milestone).first ||
- find_milestones(project, title: milestone_param.strip).first
- end
- command :milestone do |milestone|
- @updates[:milestone_id] = milestone.id if milestone
- end
-
- desc 'Remove milestone'
- explanation do
- "Removes #{issuable.milestone.to_reference(format: :name)} milestone."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.milestone_id? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :remove_milestone do
- @updates[:milestone_id] = nil
- end
-
- desc 'Add label(s)'
- explanation do |labels_param|
- labels = find_label_references(labels_param)
-
- "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
- end
- params '~label1 ~"label 2"'
- condition do
- parent &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", parent) &&
- find_labels.any?
- end
- command :label do |labels_param|
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:add_label_ids] ||= []
- @updates[:add_label_ids] += label_ids
-
- @updates[:add_label_ids].uniq!
- end
- end
-
- desc 'Remove all or specific label(s)'
- explanation do |labels_param = nil|
- if labels_param.present?
- labels = find_label_references(labels_param)
- "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
- else
- 'Removes all labels.'
- end
- end
- params '~label1 ~"label 2"'
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.labels.any? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", parent)
- end
- command :unlabel do |labels_param = nil|
- if labels_param.present?
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:remove_label_ids] ||= []
- @updates[:remove_label_ids] += label_ids
-
- @updates[:remove_label_ids].uniq!
- end
- else
- @updates[:label_ids] = []
- end
- end
-
- desc 'Replace all label(s)'
- explanation do |labels_param|
- labels = find_label_references(labels_param)
- "Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
- end
- params '~label1 ~"label 2"'
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.labels.any? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :relabel do |labels_param|
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:label_ids] ||= []
- @updates[:label_ids] += label_ids
-
- @updates[:label_ids].uniq!
- end
- end
-
- desc 'Copy labels and milestone from other issue or merge request'
- explanation do |source_issuable|
- "Copy labels and milestone from #{source_issuable.to_reference}."
- end
- params '#issue | !merge_request'
- condition do
- [MergeRequest, Issue].include?(issuable.class) &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- parse_params do |issuable_param|
- extract_references(issuable_param, :issue).first ||
- extract_references(issuable_param, :merge_request).first
- end
- command :copy_metadata do |source_issuable|
- if source_issuable.present? && source_issuable.project.id == issuable.project.id
- @updates[:add_label_ids] = source_issuable.labels.map(&:id)
- @updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
- end
- end
-
- desc 'Add a todo'
- explanation 'Adds a todo.'
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- !TodoService.new.todo_exist?(issuable, current_user)
- end
- command :todo do
- @updates[:todo_event] = 'add'
- end
-
- desc 'Mark todo as done'
- explanation 'Marks todo as done.'
- condition do
- issuable.persisted? &&
- TodoService.new.todo_exist?(issuable, current_user)
- end
- command :done do
- @updates[:todo_event] = 'done'
- end
-
- desc 'Subscribe'
- explanation do
- "Subscribes to this #{issuable.to_ability_name.humanize(capitalize: false)}."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- !issuable.subscribed?(current_user, project)
- end
- command :subscribe do
- @updates[:subscription_event] = 'subscribe'
- end
-
- desc 'Unsubscribe'
- explanation do
- "Unsubscribes from this #{issuable.to_ability_name.humanize(capitalize: false)}."
- end
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted? &&
- issuable.subscribed?(current_user, project)
- end
- command :unsubscribe do
- @updates[:subscription_event] = 'unsubscribe'
- end
-
- desc 'Set due date'
- explanation do |due_date|
- "Sets the due date to #{due_date.to_s(:medium)}." if due_date
- end
- params '<in 2 days | this Friday | December 31st>'
- condition do
- issuable.respond_to?(:due_date) &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |due_date_param|
- Chronic.parse(due_date_param).try(:to_date)
- end
- command :due do |due_date|
- @updates[:due_date] = due_date if due_date
- end
-
- desc 'Remove due date'
- explanation 'Removes the due date.'
- condition do
- issuable.persisted? &&
- issuable.respond_to?(:due_date) &&
- issuable.due_date? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :remove_due_date do
- @updates[:due_date] = nil
- end
-
- desc 'Toggle the Work In Progress status'
- explanation do
- verb = issuable.work_in_progress? ? 'Unmarks' : 'Marks'
- noun = issuable.to_ability_name.humanize(capitalize: false)
- "#{verb} this #{noun} as Work In Progress."
- end
- condition do
- issuable.respond_to?(:work_in_progress?) &&
- # Allow it to mark as WIP on MR creation page _or_ through MR notes.
- (issuable.new_record? || current_user.can?(:"update_#{issuable.to_ability_name}", issuable))
- end
- command :wip do
- @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
- end
-
- desc 'Toggle emoji award'
- explanation do |name|
- "Toggles :#{name}: emoji award." if name
- end
- params ':emoji:'
- condition do
- issuable.is_a?(Issuable) &&
- issuable.persisted?
- end
- parse_params do |emoji_param|
- match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern)
- match[1] if match
- end
- command :award do |name|
- if name && issuable.user_can_award?(current_user)
- @updates[:emoji_award] = name
- end
- end
-
- desc 'Set time estimate'
- explanation do |time_estimate|
- time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
-
- "Sets time estimate to #{time_estimate}." if time_estimate
- end
- params '<1w 3d 2h 14m>'
- condition do
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- parse_params do |raw_duration|
- Gitlab::TimeTrackingFormatter.parse(raw_duration)
- end
- command :estimate do |time_estimate|
- if time_estimate
- @updates[:time_estimate] = time_estimate
- end
- end
-
- desc 'Add or subtract spent time'
- explanation do |time_spent, time_spent_date|
- if time_spent
- if time_spent > 0
- verb = 'Adds'
- value = time_spent
- else
- verb = 'Subtracts'
- value = -time_spent
- end
-
- "#{verb} #{Gitlab::TimeTrackingFormatter.output(value)} spent time."
- end
- end
- params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
- condition do
- issuable.is_a?(TimeTrackable) &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
- end
- parse_params do |raw_time_date|
- Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
- end
- command :spend do |time_spent, time_spent_date|
- if time_spent
- @updates[:spend_time] = {
- duration: time_spent,
- user_id: current_user.id,
- spent_at: time_spent_date
- }
- end
- end
-
- desc 'Remove time estimate'
- explanation 'Removes time estimate.'
- condition do
- issuable.persisted? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :remove_estimate do
- @updates[:time_estimate] = 0
- end
-
- desc 'Remove spent time'
- explanation 'Removes spent time.'
- condition do
- issuable.persisted? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :remove_time_spent do
- @updates[:spend_time] = { duration: :reset, user_id: current_user.id }
- end
-
- desc "Append the comment with #{SHRUG}"
- params '<Comment>'
- substitution :shrug do |comment|
- "#{comment} #{SHRUG}"
- end
-
- desc "Append the comment with #{TABLEFLIP}"
- params '<Comment>'
- substitution :tableflip do |comment|
- "#{comment} #{TABLEFLIP}"
- end
-
- desc "Lock the discussion"
- explanation "Locks the discussion"
- condition do
- [MergeRequest, Issue].include?(issuable.class) &&
- issuable.persisted? &&
- !issuable.discussion_locked? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
- end
- command :lock do
- @updates[:discussion_locked] = true
- end
-
- desc "Unlock the discussion"
- explanation "Unlocks the discussion"
- condition do
- [MergeRequest, Issue].include?(issuable.class) &&
- issuable.persisted? &&
- issuable.discussion_locked? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
- end
- command :unlock do
- @updates[:discussion_locked] = false
- end
-
- # This is a dummy command, so that it appears in the autocomplete commands
- desc 'CC'
- params '@user'
- command :cc
-
- desc 'Set target branch'
- explanation do |branch_name|
- "Sets target branch to #{branch_name}."
- end
- params '<Local branch name>'
- condition do
- issuable.respond_to?(:target_branch) &&
- (current_user.can?(:"update_#{issuable.to_ability_name}", issuable) ||
- issuable.new_record?)
- end
- parse_params do |target_branch_param|
- target_branch_param.strip
- end
- command :target_branch do |branch_name|
- @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
- end
-
- desc 'Move issue from one column of the board to another'
- explanation do |target_list_name|
- label = find_label_references(target_list_name).first
- "Moves issue to #{label} column in the board." if label
- end
- params '~"Target column"'
- condition do
- issuable.is_a?(Issue) &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable) &&
- issuable.project.boards.count == 1
- end
- # rubocop: disable CodeReuse/ActiveRecord
- command :board_move do |target_list_name|
- label_ids = find_label_ids(target_list_name)
-
- if label_ids.size == 1
- label_id = label_ids.first
-
- # Ensure this label corresponds to a list on the board
- next unless Label.on_project_boards(issuable.project_id).where(id: label_id).exists?
-
- @updates[:remove_label_ids] =
- issuable.labels.on_project_boards(issuable.project_id).where.not(id: label_id).pluck(:id)
- @updates[:add_label_ids] = [label_id]
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Mark this issue as a duplicate of another issue'
- explanation do |duplicate_reference|
- "Marks this issue as a duplicate of #{duplicate_reference}."
- end
- params '#issue'
- condition do
- issuable.is_a?(Issue) &&
- issuable.persisted? &&
- current_user.can?(:"update_#{issuable.to_ability_name}", issuable)
- end
- command :duplicate do |duplicate_param|
- canonical_issue = extract_references(duplicate_param, :issue).first
-
- if canonical_issue.present?
- @updates[:canonical_issue_id] = canonical_issue.id
- end
- end
-
- desc 'Move this issue to another project.'
- explanation do |path_to_project|
- "Moves this issue to #{path_to_project}."
- end
- params 'path/to/project'
- condition do
- issuable.is_a?(Issue) &&
- issuable.persisted? &&
- current_user.can?(:"admin_#{issuable.to_ability_name}", project)
- end
- command :move do |target_project_path|
- target_project = Project.find_by_full_path(target_project_path)
-
- if target_project.present?
- @updates[:target_project] = target_project
- end
- end
-
- desc 'Make issue confidential.'
- explanation do
- 'Makes this issue confidential'
- end
- condition do
- issuable.is_a?(Issue) && current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
- end
- command :confidential do
- @updates[:confidential] = true
- end
-
- desc 'Tag this commit.'
- explanation do |tag_name, message|
- with_message = %{ with "#{message}"} if message.present?
- "Tags this commit to #{tag_name}#{with_message}."
- end
- params 'v1.2.3 <message>'
- parse_params do |tag_name_and_message|
- tag_name_and_message.split(' ', 2)
- end
- condition do
- issuable.is_a?(Commit) && current_user.can?(:push_code, project)
- end
- command :tag do |tag_name, message|
- @updates[:tag_name] = tag_name
- @updates[:tag_message] = message
- end
-
- desc 'Create a merge request.'
- explanation do |branch_name = nil|
- branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch'
- "Creates #{branch_text} and a merge request to resolve this issue"
- end
- params "<branch name>"
- condition do
- issuable.is_a?(Issue) && current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project)
- end
- command :create_merge_request do |branch_name = nil|
- @updates[:create_merge_request] = {
- branch_name: branch_name,
- issue_iid: issuable.iid
- }
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def extract_users(params)
return [] if params.nil?
@@ -680,7 +91,7 @@ module QuickActions
def group
strong_memoize(:group) do
- issuable.group if issuable.respond_to?(:group)
+ quick_action_target.group if quick_action_target.respond_to?(:group)
end
end
diff --git a/app/services/releases/destroy_service.rb b/app/services/releases/destroy_service.rb
index 8c2bc3b4e6e..f9f6101abdd 100644
--- a/app/services/releases/destroy_service.rb
+++ b/app/services/releases/destroy_service.rb
@@ -5,7 +5,6 @@ module Releases
include Releases::Concerns
def execute
- return error('Tag does not exist', 404) unless existing_tag
return error('Release does not exist', 404) unless release
return error('Access Denied', 403) unless allowed?
diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb
index f778c5aa5f5..8ba50e22b09 100644
--- a/app/services/suggestions/apply_service.rb
+++ b/app/services/suggestions/apply_service.rb
@@ -7,7 +7,7 @@ module Suggestions
end
def execute(suggestion)
- unless suggestion.appliable?
+ unless suggestion.appliable?(cached: false)
return error('Suggestion is not appliable')
end
@@ -15,7 +15,7 @@ module Suggestions
return error('The file has been changed')
end
- diff_file = suggestion.note.latest_diff_file
+ diff_file = suggestion.diff_file
unless diff_file
return error('The file was not found')
diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb
index c7ac2452c53..1d3338c1b45 100644
--- a/app/services/suggestions/create_service.rb
+++ b/app/services/suggestions/create_service.rb
@@ -9,52 +9,24 @@ module Suggestions
def execute
return unless @note.supports_suggestion?
- diff_file = @note.latest_diff_file
-
- return unless diff_file
-
- suggestions = Banzai::SuggestionsParser.parse(@note.note)
-
- # For single line suggestion we're only looking forward to
- # change the line receiving the comment. Though, in
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/53310
- # we'll introduce a ```suggestion:L<x>-<y>, so this will
- # slightly change.
- comment_line = @note.position.new_line
+ suggestions = Gitlab::Diff::SuggestionsParser.parse(@note.note,
+ project: @note.project,
+ position: @note.position)
rows =
suggestions.map.with_index do |suggestion, index|
- from_content = changing_lines(diff_file, comment_line, comment_line)
+ creation_params =
+ suggestion.to_hash.slice(:from_content,
+ :to_content,
+ :lines_above,
+ :lines_below)
- # The parsed suggestion doesn't have information about the correct
- # ending characters (we may have a line break, or not), so we take
- # this information from the last line being changed (last
- # characters).
- endline_chars = line_break_chars(from_content.lines.last)
- to_content = "#{suggestion}#{endline_chars}"
-
- {
- note_id: @note.id,
- from_content: from_content,
- to_content: to_content,
- relative_order: index
- }
+ creation_params.merge!(note_id: @note.id, relative_order: index)
end
rows.in_groups_of(100, false) do |rows|
Gitlab::Database.bulk_insert('suggestions', rows)
end
end
-
- private
-
- def changing_lines(diff_file, from_line, to_line)
- diff_file.new_blob_lines_between(from_line, to_line).join
- end
-
- def line_break_chars(line)
- match = /\r\n|\r|\n/.match(line)
- match[0] if match
- end
end
end
diff --git a/app/services/suggestions/outdate_service.rb b/app/services/suggestions/outdate_service.rb
new file mode 100644
index 00000000000..a33aac9f6b5
--- /dev/null
+++ b/app/services/suggestions/outdate_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Suggestions
+ class OutdateService
+ def execute(merge_request)
+ # rubocop: disable CodeReuse/ActiveRecord
+ suggestions = merge_request.suggestions.active.includes(:note)
+
+ suggestions.find_in_batches(batch_size: 100) do |group|
+ outdatable_suggestion_ids = group.select do |suggestion|
+ suggestion.outdated?(cached: false)
+ end.map(&:id)
+
+ Suggestion.where(id: outdatable_suggestion_ids).update_all(outdated: true)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+end
diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb
index dad6e85fb56..5f8b89f2a24 100644
--- a/app/uploaders/object_storage.rb
+++ b/app/uploaders/object_storage.rb
@@ -278,8 +278,12 @@ module ObjectStorage
self.class.object_store_credentials
end
+ # Set ACL of uploaded objects to not-public (fog-aws)[1] or no ACL at all
+ # (fog-google). Value is ignored by other supported backends (fog-aliyun,
+ # fog-openstack, fog-rackspace)
+ # [1]: https://github.com/fog/fog-aws/blob/daa50bb3717a462baf4d04d0e0cbfc18baacb541/lib/fog/aws/models/storage/file.rb#L152-L159
def fog_public
- false
+ nil
end
def delete_migrated_file(migrated_file)
diff --git a/app/validators/cluster_name_validator.rb b/app/validators/cluster_name_validator.rb
index 85fd63f08e5..79c9c67ae58 100644
--- a/app/validators/cluster_name_validator.rb
+++ b/app/validators/cluster_name_validator.rb
@@ -5,7 +5,9 @@
# Custom validator for ClusterName.
class ClusterNameValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- if record.managed?
+ if record.provided_by_user?
+ record.errors.add(attribute, " has to be present") unless value.present?
+ else
if record.persisted? && record.name_changed?
record.errors.add(attribute, " can not be changed because it's synchronized with provider")
end
@@ -17,10 +19,6 @@ class ClusterNameValidator < ActiveModel::EachValidator
unless value =~ Gitlab::Regex.kubernetes_namespace_regex
record.errors.add(attribute, Gitlab::Regex.kubernetes_namespace_regex_message)
end
- else
- unless value.present?
- record.errors.add(attribute, " has to be present")
- end
end
end
end
diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
index ecf2b1d60ba..423472324fe 100644
--- a/app/views/admin/runners/_runner.html.haml
+++ b/app/views/admin/runners/_runner.html.haml
@@ -50,7 +50,7 @@
.table-mobile-header{ role: 'rowheader' }= _('Tags')
.table-mobile-content
- runner.tags.map(&:name).sort.each do |tag|
- %span.badge.badge-primary
+ %span.badge.badge-primary.str-truncated.has-tooltip{ title: tag }
= tag
.table-section.section-10
diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index 7037c80aa6b..8005dcbf65f 100644
--- a/app/views/clusters/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -1,5 +1,5 @@
- if can?(current_user, :admin_cluster, @cluster)
- - if @cluster.managed?
+ - unless @cluster.provided_by_user?
.append-bottom-20
%label.append-bottom-10
= s_('ClusterIntegration|Google Kubernetes Engine')
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 61188c6fa0b..62b947ca40d 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -53,5 +53,5 @@
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
- .settings-content
+ .settings-content#advanced-settings-section
= render 'advanced_settings'
diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml
index 4a452b83112..b5ddca7ccb9 100644
--- a/app/views/clusters/platforms/kubernetes/_form.html.haml
+++ b/app/views/clusters/platforms/kubernetes/_form.html.haml
@@ -2,7 +2,7 @@
= form_errors(cluster)
.form-group
- - if cluster.managed?
+ - if cluster.read_only_kubernetes_platform_fields?
%label.append-bottom-10{ for: 'cluster-name' }
= s_('ClusterIntegration|Kubernetes cluster name')
.input-group
@@ -18,27 +18,27 @@
.form-group
= platform_field.label :api_url, s_('ClusterIntegration|API URL')
.input-group
- = platform_field.text_field :api_url, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|API URL'), readonly: cluster.managed?
- - if cluster.managed?
+ = platform_field.text_field :api_url, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|API URL'), readonly: cluster.read_only_kubernetes_platform_fields?
+ - if cluster.read_only_kubernetes_platform_fields?
%span.input-group-append
= clipboard_button(text: platform.api_url, title: s_('ClusterIntegration|Copy API URL'), class: 'input-group-text btn-default')
.form-group
= platform_field.label :ca_cert, s_('ClusterIntegration|CA Certificate')
.input-group
- = platform_field.text_area :ca_cert, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), readonly: cluster.managed?
- - if cluster.managed?
+ = platform_field.text_area :ca_cert, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), readonly: cluster.read_only_kubernetes_platform_fields?
+ - if cluster.read_only_kubernetes_platform_fields?
%span.input-group-append.clipboard-addon
= clipboard_button(text: platform.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'), class: 'input-group-text btn-blank')
.form-group
= platform_field.label :token, s_('ClusterIntegration|Token')
.input-group
- = platform_field.text_field :token, class: 'form-control js-cluster-token js-select-on-focus', type: 'password', placeholder: s_('ClusterIntegration|Token'), readonly: cluster.managed?
+ = platform_field.text_field :token, class: 'form-control js-cluster-token js-select-on-focus', type: 'password', placeholder: s_('ClusterIntegration|Token'), readonly: cluster.read_only_kubernetes_platform_fields?
%span.input-group-append
%button.btn.btn-default.input-group-text.js-show-cluster-token{ type: 'button' }
= s_('ClusterIntegration|Show')
- - if cluster.managed?
+ - if cluster.read_only_kubernetes_platform_fields?
= clipboard_button(text: platform.token, title: s_('ClusterIntegration|Copy Token'), class: 'btn-default')
- if cluster.allow_user_defined_namespace?
diff --git a/app/views/dashboard/groups/_groups.html.haml b/app/views/dashboard/groups/_groups.html.haml
index db856ef7d7b..2f9dbf87d95 100644
--- a/app/views/dashboard/groups/_groups.html.haml
+++ b/app/views/dashboard/groups/_groups.html.haml
@@ -1,4 +1,4 @@
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml
index 44c898e0fac..8a3c841de0b 100644
--- a/app/views/discussions/_diff_with_notes.html.haml
+++ b/app/views/discussions/_diff_with_notes.html.haml
@@ -11,8 +11,8 @@
= render "projects/diffs/file_header", diff_file: diff_file, url: discussion_path(discussion), show_toggle: false
- if diff_file.text?
- .diff-content.code.js-syntax-highlight
- %table
+ .diff-content
+ %table.code.js-syntax-highlight
- if expanded
- discussions = { discussion.original_line_code => [discussion] }
= render partial: "projects/diffs/line",
diff --git a/app/views/explore/groups/_groups.html.haml b/app/views/explore/groups/_groups.html.haml
index ff57b39e947..a3249275d5e 100644
--- a/app/views/explore/groups/_groups.html.haml
+++ b/app/views/explore/groups/_groups.html.haml
@@ -1,4 +1,4 @@
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: explore_groups_path(format: :json), path: explore_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/groups/_archived_projects.html.haml b/app/views/groups/_archived_projects.html.haml
index ed79f5790f0..48e9f630050 100644
--- a/app/views/groups/_archived_projects.html.haml
+++ b/app/views/groups/_archived_projects.html.haml
@@ -4,5 +4,5 @@
%ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
.js-groups-list-holder
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/groups/_shared_projects.html.haml b/app/views/groups/_shared_projects.html.haml
index 4eb8367f633..2769b69add3 100644
--- a/app/views/groups/_shared_projects.html.haml
+++ b/app/views/groups/_shared_projects.html.haml
@@ -4,5 +4,5 @@
%ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
.js-groups-list-holder
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/groups/_subgroups_and_projects.html.haml b/app/views/groups/_subgroups_and_projects.html.haml
index d53c8026df8..784f5ac233e 100644
--- a/app/views/groups/_subgroups_and_projects.html.haml
+++ b/app/views/groups/_subgroups_and_projects.html.haml
@@ -4,5 +4,5 @@
%ul.content-list{ data: { hide_projects: 'false', group_id: group.id, path: group_path(group) } }
.js-groups-list-holder
- .loading-container.text-center
- = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
+ .loading-container.text-center.prepend-top-20
+ .spinner.spinner-md
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index dfa5d820ce9..f40fdb0b86b 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -29,7 +29,7 @@
.row.prepend-top-default
.col-md-8
- .documentation-index.wiki
+ .documentation-index.md
= markdown(@help_index)
.col-md-4
.card
diff --git a/app/views/help/instance_configuration.html.haml b/app/views/help/instance_configuration.html.haml
index f09e3825a4b..99576d45f76 100644
--- a/app/views/help/instance_configuration.html.haml
+++ b/app/views/help/instance_configuration.html.haml
@@ -1,5 +1,5 @@
- page_title 'Instance Configuration'
-.wiki.documentation
+.documentation.md
%h1 Instance Configuration
%p
diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml
index c07c148a12a..dce27dee9be 100644
--- a/app/views/help/show.html.haml
+++ b/app/views/help/show.html.haml
@@ -1,3 +1,3 @@
- page_title @path.split("/").reverse.map(&:humanize)
-.documentation.wiki.prepend-top-default
+.documentation.md.prepend-top-default
= markdown @markdown
diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml
index 506f580b246..969df69aafb 100644
--- a/app/views/help/ui.html.haml
+++ b/app/views/help/ui.html.haml
@@ -513,7 +513,7 @@
%h2#markdown Markdown
%h4
- %code .md or .wiki and others
+ %code .md
Markdown rendering has a bit different css and presented in next UI elements:
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index eefe86eb6b4..b950e53639a 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -45,6 +45,8 @@
%span
= _('Contribution Analytics')
+ = render_if_exists 'layouts/nav/group_insights_link'
+
= render_if_exists "layouts/nav/ee/epic_link", group: @group
- if group_sidebar_link?(:issues)
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 7b492efeb09..3a0c2b9c284 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -41,6 +41,8 @@
= link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
%span= _('Cycle Analytics')
+ = render_if_exists 'layouts/nav/project_insights_link'
+
- if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths) do
= link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
@@ -355,12 +357,12 @@
= link_to project_settings_repository_path(@project), title: _('Repository') do
%span
= _('Repository')
- - if @project.feature_available?(:builds, current_user)
+ - if !@project.archived? && @project.feature_available?(:builds, current_user)
= nav_link(controller: :ci_cd) do
= link_to project_settings_ci_cd_path(@project), title: _('CI / CD') do
%span
= _('CI / CD')
- - if settings_operations_available?
+ - if !@project.archived? && settings_operations_available?
= nav_link(controller: [:operations]) do
= link_to project_settings_operations_path(@project), title: _('Operations') do
= _('Operations')
diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb
index 5a67214059c..fae8fa3ccf3 100644
--- a/app/views/notify/_note_email.text.erb
+++ b/app/views/notify/_note_email.text.erb
@@ -1,5 +1,6 @@
<% note = local_assigns.fetch(:note, @note) -%>
<% diff_limit = local_assigns.fetch(:diff_limit, nil) -%>
+<% target_url = local_assigns.fetch(:target_url, @target_url) -%>
<% discussion = note.discussion if note.part_of_discussion? -%>
<% if discussion && !discussion.individual_note? -%>
@@ -13,6 +14,9 @@
<%= " on #{discussion.file_path}" -%>
<% end -%>
<%= ":" -%>
+<% if discussion.diff_discussion? || !discussion.new_discussion? -%>
+<%= " #{target_url}" -%>
+<% end -%>
<% elsif Gitlab::CurrentSettings.email_author_in_body -%>
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 1fffea08dae..02c750a92c3 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -99,10 +99,10 @@
.checkbox-icon-inline-wrapper
- private_profile_label = capture do
= s_("Profiles|Don't display activity-related personal information on your profiles")
- = f.check_box :private_profile, label: private_profile_label
+ = f.check_box :private_profile, label: private_profile_label, inline: true, wrapper_class: 'mr-0'
= link_to icon('question-circle'), help_page_path('user/profile/index.md', anchor: 'private-profile')
%h5= s_("Profiles|Private contributions")
- = f.check_box :include_private_contributions, label: 'Include private contributions on my profile'
+ = f.check_box :include_private_contributions, label: 'Include private contributions on my profile', wrapper_class: 'mb-2', inline: true
.help-block
= s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information")
.prepend-top-default.append-bottom-default
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 830cfa80d58..10575aa68b1 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -7,7 +7,7 @@
= _('This merge request is locked.')
= _('Only project members can comment.')
-.md-area
+.md-area.position-relative
.md-header
%ul.nav.nav-tabs.nav-links.clearfix
%li.md-header-tab.active
@@ -22,7 +22,7 @@
.md-write-holder
= yield
- .md.md-preview-holder.js-md-preview.hide.md-preview{ data: { url: url } }
+ .md.md-preview-holder.js-md-preview.hide{ data: { url: url } }
.referenced-commands.hide
- if referenced_users
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index de4653dad2c..6103d86bf5a 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -1,8 +1,7 @@
- if @wiki_home.present?
%div{ class: container_class }
- .prepend-top-default.append-bottom-default
- .wiki
- = render_wiki_content(@wiki_home)
+ .md.md-file.prepend-top-default.append-bottom-default
+ = render_wiki_content(@wiki_home)
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
.landing{ class: [('row-content-block row p-0 align-items-center' if can_create_wiki), ('content-block' unless can_create_wiki)] }
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 09295940529..6a7cb1499c5 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -4,7 +4,7 @@
= render "projects/jobs/header"
- add_to_breadcrumbs(s_('CICD|Jobs'), project_jobs_path(@project))
-- add_to_breadcrumbs("##{@build.id}", project_jobs_path(@project))
+- add_to_breadcrumbs("##{@build.id}", project_job_path(@project, @build))
.tree-holder
.nav-block
diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml
index ea7a71792a3..4f3db61f688 100644
--- a/app/views/projects/blob/diff.html.haml
+++ b/app/views/projects/blob/diff.html.haml
@@ -15,14 +15,14 @@
%a{ href: "#", data: { linenumber: line_number_old }, disabled: true }
%td.new_line.diff-line-num{ data: { linenumber: line_number_new } }
%a{ href: "#", data: { linenumber: line_number_new }, disabled: true }
- %td.line_content.noteable_line{ class: line_class }= line
+ %td.line_content{ class: line_class }= line
- when :parallel
%td.old_line.diff-line-num{ data: { linenumber: line_number_old } }
%a{ href: "##{line_number_old}", data: { linenumber: line_number_old }, disabled: true }
- %td.line_content.noteable_line.left-side{ class: line_class }= line
+ %td.line_content.left-side{ class: line_class }= line
%td.new_line.diff-line-num{ data: { linenumber: line_number_new } }
%a{ href: "##{line_number_new}", data: { linenumber: line_number_new }, disabled: true }
- %td.line_content.noteable_line.right-side{ class: line_class }= line
+ %td.line_content.right-side{ class: line_class }= line
- if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size
%tr.line_holder{ id: @form.to, class: line_class }
diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml
index 66687f087ff..3e893343165 100644
--- a/app/views/projects/blob/preview.html.haml
+++ b/app/views/projects/blob/preview.html.haml
@@ -1,21 +1,20 @@
-.diff-file.file-holder
- .diff-content
- - if markup?(@blob.name)
- .file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
- = markup(@blob.name, @content)
- - else
- .file-content.code.js-syntax-highlight
- - unless @diff_lines.empty?
- %table.text-file
- - @diff_lines.each do |line|
- %tr.line_holder{ class: "#{line.type}" }
- - if line.type == "match"
- %td.old_line.diff-line-num= "..."
- %td.new_line.diff-line-num= "..."
- %td.line_content.match= line.text
- - else
- %td.old_line.diff-line-num
- %td.new_line.diff-line-num
- %td.line_content{ class: "#{line.type}" }= diff_line_content(line.text)
- - else
- .nothing-here-block No changes.
+- if markup?(@blob.name)
+ .file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ = markup(@blob.name, @content)
+- else
+ .diff-file
+ .diff-content
+ - unless @diff_lines.empty?
+ %table.text-file.code.js-syntax-highlight
+ - @diff_lines.each do |line|
+ %tr.line_holder{ class: line.type }
+ - if line.type == "match"
+ %td.old_line.diff-line-num.match= "..."
+ %td.new_line.diff-line-num.match= "..."
+ %td.line_content.match= line.text
+ - else
+ %td.old_line.diff-line-num{ class: line.type }
+ %td.new_line.diff-line-num{ class: line.type }
+ %td.line_content{ class: line.type }= diff_line_content(line.text)
+ - else
+ .nothing-here-block No changes.
diff --git a/app/views/projects/blob/viewers/_markup.html.haml b/app/views/projects/blob/viewers/_markup.html.haml
index 1a77eb078be..abc74b66e90 100644
--- a/app/views/projects/blob/viewers/_markup.html.haml
+++ b/app/views/projects/blob/viewers/_markup.html.haml
@@ -1,4 +1,4 @@
- blob = viewer.blob
- context = blob.respond_to?(:rendered_markup) ? { rendered: blob.rendered_markup } : {}
-.file-content.wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+.file-content.md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= markup(blob.name, blob.data, context)
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index ffdca500abe..d35443cca1e 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -30,7 +30,7 @@
= link_text
- else
%a{ href: "##{line_code}", data: { linenumber: link_text } }
- %td.line_content.noteable_line{ class: type }<
+ %td.line_content{ class: type }<
- if email
%pre= line.rich_text
- else
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index 4b1d4b3ea17..311b0be19ab 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -1,7 +1,7 @@
/ Side-by-side diff view
-.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data }
- %table
+.text-file{ data: diff_view_data }
+ %table.diff-wrap-lines.code.js-syntax-highlight
- diff_file.parallel_diff_lines.each do |line|
- left = line[:left]
- right = line[:right]
@@ -23,7 +23,7 @@
- discussion_left = discussions_left.try(:first)
- if discussion_left && discussion_left.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_left.id }
- %td.line_content.parallel.noteable_line.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.rich_text)
+ %td.line_content.parallel.left-side{ id: left_line_code, class: left.type }= diff_line_content(left.rich_text)
- else
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel.left-side
@@ -44,7 +44,7 @@
- discussion_right = discussions_right.try(:first)
- if discussion_right && discussion_right.resolvable?
%diff-note-avatars{ "discussion-id" => discussion_right.id }
- %td.line_content.parallel.noteable_line.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.rich_text)
+ %td.line_content.parallel.right-side{ id: right_line_code, class: right.type }= diff_line_content(right.rich_text)
- else
%td.old_line.diff-line-num.empty-cell
%td.line_content.parallel.right-side
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 819d3c4ec76..f8969fbb5a2 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -72,7 +72,7 @@
%h2.title= markdown_field(@issue, :title)
- if @issue.description.present?
.description{ class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : '' }
- .wiki= markdown_field(@issue, :description)
+ .md= markdown_field(@issue, :description)
%textarea.hidden.js-task-list-field= @issue.description
= edited_time_ago_with_tooltip(@issue, placement: 'bottom', html_class: 'issue-edited-ago js-issue-edited-ago')
diff --git a/app/views/projects/merge_requests/_mr_box.html.haml b/app/views/projects/merge_requests/_mr_box.html.haml
index 1a9ab288683..7f2c9dcacfd 100644
--- a/app/views/projects/merge_requests/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/_mr_box.html.haml
@@ -5,7 +5,7 @@
%div
- if @merge_request.description.present?
.description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' }
- .wiki
+ .md
= markdown_field(@merge_request, :description)
%textarea.hidden.js-task-list-field
= @merge_request.description
diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
index d828cb6cf9e..03226de120d 100644
--- a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
+++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml
@@ -1,5 +1,5 @@
%inline-conflict-lines{ "inline-template" => "true", ":file" => "file" }
- %table
+ %table.diff-wrap-lines.code.js-syntax-highlight
%tr.line_holder.diff-inline{ "v-for" => "line in file.inlineLines" }
%td.diff-line-num.new_line{ ":class" => "lineCssClass(line)", "v-if" => "!line.isHeader" }
%a {{line.new_line}}
diff --git a/app/views/projects/merge_requests/conflicts/show.html.haml b/app/views/projects/merge_requests/conflicts/show.html.haml
index 09aeb81671a..f48390aa046 100644
--- a/app/views/projects/merge_requests/conflicts/show.html.haml
+++ b/app/views/projects/merge_requests/conflicts/show.html.haml
@@ -26,9 +26,9 @@
%strong {{file.filePath}}
= render partial: 'projects/merge_requests/conflicts/file_actions'
.diff-content.diff-wrap-lines
- .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+ .file-content{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
= render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines"
- .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
+ .file-content{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" }
%parallel-conflict-lines{ ":file" => "file" }
%div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" }
= render partial: "projects/merge_requests/conflicts/components/diff_file_editor"
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 0542b349e44..78b416edd5c 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -54,9 +54,8 @@
%div
- if @milestone.description.present?
- .description
- .wiki
- = markdown_field(@milestone, :description)
+ .description.md
+ = markdown_field(@milestone, :description)
= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index bfb275b9ef5..b38b8e3f686 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -102,17 +102,20 @@
tap --coverage-report=text-summary (NodeJS) -
%code ^Statements\s*:\s*([^%]+)
%li
+ nyc npm test (NodeJS) -
+ %code All files[^|]*\|[^|]*\s+([\d\.]+)
+ %li
excoveralls (Elixir) -
%code \[TOTAL\]\s+(\d+\.\d+)%
%li
+ mix test --cover (Elixir) -
+ %code \d+.\d+\%\s+\|\s+Total
+ %li
JaCoCo (Java/Kotlin)
%code Total.*?([0-9]{1,3})%
%li
go test -cover (Go)
%code coverage: \d+.\d+% of statements
- %li
- nyc npm test (NodeJS) -
- %code All files[^|]*\|[^|]*\s+([\d\.]+)
= f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index cc203cfad86..8bfface3f5a 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -20,9 +20,8 @@
%p
= s_("TagsPage|Can't find HEAD commit for this tag")
- if release && release.description.present?
- .description.prepend-top-default
- .wiki
- = markdown_field(release, :description)
+ .description.md.prepend-top-default
+ = markdown_field(release, :description)
.row-fixed-content.controls.flex-row
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 8ee1407d9d9..0be62bc5612 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -39,8 +39,7 @@
.append-bottom-default.prepend-top-default
- if @release.description.present?
- .description
- .wiki
- = markdown_field(@release, :description)
+ .description.md
+ = markdown_field(@release, :description)
- else
= s_('TagsPage|This tag has no release notes.')
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 8e1c054b50c..40d674f3fec 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -26,7 +26,7 @@
= (s_("WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}.") % { most_recent_link: most_recent_link, history_link: history_link }).html_safe
.prepend-top-default.append-bottom-default
- .wiki.md{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
+ .md.md-file{ class: ('use-csslab' if Feature.enabled?(:csslab)) }
= render_wiki_content(@page)
= render 'sidebar'
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 2e62039b90a..5b25a67bc87 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -21,6 +21,8 @@
- if @scope == 'projects'
.term
= render 'shared/projects/list', projects: @search_objects, pipeline_status: false
+ - elsif %w[blobs wiki_blobs].include?(@scope)
+ = render partial: 'search/results/blob', collection: @search_objects, locals: { projects: blob_projects(@search_objects) }
- else
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 2a602095845..bdad07f36d1 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,4 +1,4 @@
-- project = find_project_for_result_blob(blob)
+- project = find_project_for_result_blob(projects, blob)
- return unless project
- blob = parse_search_result(blob)
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index a60a4501557..f17dae0a94c 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -18,7 +18,7 @@
%i.fa.fa-file
%strong= snippet.file_name
- if markup?(snippet.file_name)
- .file-content.wiki
+ .file-content.md.md-file
- snippet_chunks.each do |chunk|
- unless chunk[:data].empty?
= markup(snippet.file_name, chunk[:data])
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index 389e4cc75b9..b351ecd4edf 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,4 +1,4 @@
-- project = find_project_for_result_blob(wiki_blob)
+- project = find_project_for_result_blob(projects, wiki_blob)
- wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index e75f0a184ea..e99aa3f1ee4 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -52,7 +52,7 @@
= link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close btn-grouped"
- unless milestone.active?
- = link_to 'Reopen Milestone', project_milestone_path(@project, milestone, {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
+ = link_to 'Reopen Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
- if @group
- if can?(current_user, :admin_milestone, @group)
- if milestone.closed?
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 55b1c14022f..edaeff782de 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -42,9 +42,8 @@
= markdown_field(milestone, :title)
- if milestone.group_milestone? && milestone.description.present?
%div
- .description
- .wiki
- = markdown_field(milestone, :description)
+ .description.md
+ = markdown_field(milestone, :description)
- if milestone.complete?(current_user) && milestone.active?
.alert.alert-success.prepend-top-default
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 0c07eae8643..ebb634fe75f 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -22,7 +22,7 @@
- if @snippet.description.present?
.description.qa-snippet-description
- .wiki
+ .md
= markdown_field(@snippet, :description)
%textarea.hidden.js-task-list-field
= @snippet.description
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 6ebd756d3da..3e8c2a1209a 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -21,6 +21,7 @@
- cronjob:trending_projects
- cronjob:issue_due_scheduler
- cronjob:prune_web_hook_logs
+- cronjob:schedule_migrate_external_diffs
- gcp_cluster:cluster_install_app
- gcp_cluster:cluster_patch_app
@@ -119,6 +120,7 @@
- invalid_gpg_signature_update
- irker
- merge
+- migrate_external_diffs
- namespaceless_project_destroy
- new_issue
- new_merge_request
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
index 2827529cc1c..7fac7822cf7 100644
--- a/app/workers/create_gpg_signature_worker.rb
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -5,8 +5,8 @@ class CreateGpgSignatureWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(commit_shas, project_id)
- # Older versions of GitPushService may push a single commit ID on the stack.
- # We need this to be backwards compatible.
+ # Older versions of Git::BranchPushService may push a single commit ID on
+ # the stack. We need this to be backwards compatible.
commit_shas = Array(commit_shas)
return if commit_shas.empty?
diff --git a/app/workers/migrate_external_diffs_worker.rb b/app/workers/migrate_external_diffs_worker.rb
new file mode 100644
index 00000000000..debac97af2c
--- /dev/null
+++ b/app/workers/migrate_external_diffs_worker.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: false
+
+class MigrateExternalDiffsWorker
+ include ApplicationWorker
+
+ def perform(merge_request_diff_id)
+ diff = MergeRequestDiff.find_by_id(merge_request_diff_id)
+ return unless diff
+
+ MergeRequests::MigrateExternalDiffsService.new(diff).execute
+ end
+end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index bbd4ab159e4..396f44396a3 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -4,7 +4,7 @@ class PostReceive
include ApplicationWorker
def perform(gl_repository, identifier, changes, push_options = [])
- project, is_wiki = Gitlab::GlRepository.parse(gl_repository)
+ project, repo_type = Gitlab::GlRepository.parse(gl_repository)
if project.nil?
log("Triggered hook for non-existing project with gl_repository \"#{gl_repository}\"")
@@ -17,7 +17,7 @@ class PostReceive
Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS']
post_received = Gitlab::GitPostReceive.new(project, identifier, changes, push_options)
- if is_wiki
+ if repo_type.wiki?
process_wiki_changes(post_received)
else
process_project_changes(post_received)
@@ -38,7 +38,7 @@ class PostReceive
post_received.changes_refs do |oldrev, newrev, ref|
if Gitlab::Git.tag_ref?(ref)
- GitTagPushService.new(
+ Git::TagPushService.new(
post_received.project,
@user,
oldrev: oldrev,
@@ -46,7 +46,7 @@ class PostReceive
ref: ref,
push_options: post_received.push_options).execute
elsif Gitlab::Git.branch_ref?(ref)
- GitPushService.new(
+ Git::BranchPushService.new(
post_received.project,
@user,
oldrev: oldrev,
diff --git a/app/workers/schedule_migrate_external_diffs_worker.rb b/app/workers/schedule_migrate_external_diffs_worker.rb
new file mode 100644
index 00000000000..70910f7ca04
--- /dev/null
+++ b/app/workers/schedule_migrate_external_diffs_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: false
+
+class ScheduleMigrateExternalDiffsWorker
+ include ApplicationWorker
+ include CronjobQueue
+ include Gitlab::ExclusiveLeaseHelpers
+
+ def perform
+ in_lock(self.class.name.underscore, ttl: 2.hours, retries: 0) do
+ MergeRequests::MigrateExternalDiffsService.enqueue!
+ end
+ rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ end
+end
diff --git a/changelogs/unreleased/10735-geo-gitlab-revision-can-return-not-consistent-results.yml b/changelogs/unreleased/10735-geo-gitlab-revision-can-return-not-consistent-results.yml
new file mode 100644
index 00000000000..2360295b022
--- /dev/null
+++ b/changelogs/unreleased/10735-geo-gitlab-revision-can-return-not-consistent-results.yml
@@ -0,0 +1,5 @@
+---
+title: Use a fixed git abbrev parameter when we fetch a git revision
+merge_request: 26707
+author:
+type: fixed
diff --git a/changelogs/unreleased/24936-remove-type-from-review-app-name.yml b/changelogs/unreleased/24936-remove-type-from-review-app-name.yml
new file mode 100644
index 00000000000..639333264f6
--- /dev/null
+++ b/changelogs/unreleased/24936-remove-type-from-review-app-name.yml
@@ -0,0 +1,5 @@
+---
+title: Remove unnecessary folder prefix from environment name
+merge_request: 25600
+author:
+type: changed
diff --git a/changelogs/unreleased/48132-display-output-from-pre-receive-scripts.yml b/changelogs/unreleased/48132-display-output-from-pre-receive-scripts.yml
new file mode 100644
index 00000000000..e06a4d5ee75
--- /dev/null
+++ b/changelogs/unreleased/48132-display-output-from-pre-receive-scripts.yml
@@ -0,0 +1,5 @@
+---
+title: "Allow failed custom hook script errors to safely appear in GitLab UI by filtering error messages by the prefix GL-HOOK-ERR:"
+merge_request: 25625
+author:
+type: changed
diff --git a/changelogs/unreleased/49856-upgrade-bootstrap-form-gem.yml b/changelogs/unreleased/49856-upgrade-bootstrap-form-gem.yml
new file mode 100644
index 00000000000..1dc8d5b4179
--- /dev/null
+++ b/changelogs/unreleased/49856-upgrade-bootstrap-form-gem.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade bootstrap_form Gem
+merge_request: 26568
+author:
+type: other
diff --git a/changelogs/unreleased/49910-reopening-a-closed-milestone-from-the-closed-milestones-page-fails2.yml b/changelogs/unreleased/49910-reopening-a-closed-milestone-from-the-closed-milestones-page-fails2.yml
new file mode 100644
index 00000000000..68d38cd56c5
--- /dev/null
+++ b/changelogs/unreleased/49910-reopening-a-closed-milestone-from-the-closed-milestones-page-fails2.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug when reopening milestone from index page
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/51988-install-group-runner-on-group-cluster.yml b/changelogs/unreleased/51988-install-group-runner-on-group-cluster.yml
new file mode 100644
index 00000000000..86f08dd1798
--- /dev/null
+++ b/changelogs/unreleased/51988-install-group-runner-on-group-cluster.yml
@@ -0,0 +1,5 @@
+---
+title: Support installing Group runner on group-level cluster
+merge_request: 26260
+author:
+type: added
diff --git a/changelogs/unreleased/52366-improved-group-lists-ui-spinners.yml b/changelogs/unreleased/52366-improved-group-lists-ui-spinners.yml
new file mode 100644
index 00000000000..ab09272eaf4
--- /dev/null
+++ b/changelogs/unreleased/52366-improved-group-lists-ui-spinners.yml
@@ -0,0 +1,5 @@
+---
+title: Update spinners in group list component
+merge_request: 26572
+author:
+type: changed
diff --git a/changelogs/unreleased/53210-add-uniq-constraints-on-issues-and-mrs-labels.yml b/changelogs/unreleased/53210-add-uniq-constraints-on-issues-and-mrs-labels.yml
new file mode 100644
index 00000000000..f9cd8716b92
--- /dev/null
+++ b/changelogs/unreleased/53210-add-uniq-constraints-on-issues-and-mrs-labels.yml
@@ -0,0 +1,5 @@
+---
+title: add a uniq constraints on issues and mrs labels
+merge_request: 25435
+author: Antoine Huret
+type: fixed
diff --git a/changelogs/unreleased/54670-external-diffs-when-outdated.yml b/changelogs/unreleased/54670-external-diffs-when-outdated.yml
new file mode 100644
index 00000000000..2a0b9e75cb4
--- /dev/null
+++ b/changelogs/unreleased/54670-external-diffs-when-outdated.yml
@@ -0,0 +1,5 @@
+---
+title: 'Allow external diffs to be used conditionally'
+merge_request: 25432
+author:
+type: added
diff --git a/changelogs/unreleased/54916-extended-tooltip-for-merge-request-links.yml b/changelogs/unreleased/54916-extended-tooltip-for-merge-request-links.yml
new file mode 100644
index 00000000000..7fd0bcd1c00
--- /dev/null
+++ b/changelogs/unreleased/54916-extended-tooltip-for-merge-request-links.yml
@@ -0,0 +1,5 @@
+---
+title: Add extended merge request tooltip
+merge_request: !25221
+author:
+type: added
diff --git a/changelogs/unreleased/57669-fix-bug-clicking-file-header-refreshes-page.yml b/changelogs/unreleased/57669-fix-bug-clicking-file-header-refreshes-page.yml
new file mode 100644
index 00000000000..c6161870096
--- /dev/null
+++ b/changelogs/unreleased/57669-fix-bug-clicking-file-header-refreshes-page.yml
@@ -0,0 +1,6 @@
+---
+title: Scroll to diff file content when clicking on file header name and it is not
+ a link to other page
+merge_request: !26422
+author:
+type: fixed
diff --git a/changelogs/unreleased/58793-fix-nav-links-archived-project.yml b/changelogs/unreleased/58793-fix-nav-links-archived-project.yml
new file mode 100644
index 00000000000..a8250804c34
--- /dev/null
+++ b/changelogs/unreleased/58793-fix-nav-links-archived-project.yml
@@ -0,0 +1,5 @@
+---
+title: "Disable inaccessible navigation links upon archiving a project"
+merge_request: 26020
+author: Elias Werberich
+type: fixed
diff --git a/changelogs/unreleased/58805-allow-incomplete-commit-data-to-be-fetched-from-collection.yml b/changelogs/unreleased/58805-allow-incomplete-commit-data-to-be-fetched-from-collection.yml
new file mode 100644
index 00000000000..4377ebfdbdf
--- /dev/null
+++ b/changelogs/unreleased/58805-allow-incomplete-commit-data-to-be-fetched-from-collection.yml
@@ -0,0 +1,5 @@
+---
+title: Fix merge commits being used as default squash commit messages
+merge_request: 26445
+author:
+type: fixed
diff --git a/changelogs/unreleased/58971-sentry-api-keyerror.yml b/changelogs/unreleased/58971-sentry-api-keyerror.yml
new file mode 100644
index 00000000000..0f195c4b4f7
--- /dev/null
+++ b/changelogs/unreleased/58971-sentry-api-keyerror.yml
@@ -0,0 +1,5 @@
+---
+title: Handle missing keys in sentry api response
+merge_request: 26264
+author:
+type: fixed
diff --git a/changelogs/unreleased/59062-update-gitlab-markup-python-3.yml b/changelogs/unreleased/59062-update-gitlab-markup-python-3.yml
new file mode 100644
index 00000000000..265a7e36841
--- /dev/null
+++ b/changelogs/unreleased/59062-update-gitlab-markup-python-3.yml
@@ -0,0 +1,5 @@
+---
+title: Update gitlab-markup to 1.7.0 which requies python3
+merge_request: 26246
+author:
+type: changed
diff --git a/changelogs/unreleased/59079-fix-jupyter-render-loop.yml b/changelogs/unreleased/59079-fix-jupyter-render-loop.yml
new file mode 100644
index 00000000000..29264b33dfa
--- /dev/null
+++ b/changelogs/unreleased/59079-fix-jupyter-render-loop.yml
@@ -0,0 +1,5 @@
+---
+title: Fix jupyter rendering bug that ended in an infinite loop
+merge_request: 26656
+author: ROSPARS Benoit
+type: fixed
diff --git a/changelogs/unreleased/59273-update-fugit.yml b/changelogs/unreleased/59273-update-fugit.yml
new file mode 100644
index 00000000000..3a1c64d87ef
--- /dev/null
+++ b/changelogs/unreleased/59273-update-fugit.yml
@@ -0,0 +1,5 @@
+---
+title: Update fugit which fixes a potential infinite loop
+merge_request: 26579
+author:
+type: fixed
diff --git a/changelogs/unreleased/59296-add-filter-by-title-milestones-api.yml b/changelogs/unreleased/59296-add-filter-by-title-milestones-api.yml
new file mode 100644
index 00000000000..440b24a548c
--- /dev/null
+++ b/changelogs/unreleased/59296-add-filter-by-title-milestones-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add select by title to milestones API
+merge_request: 26573
+author:
+type: added
diff --git a/changelogs/unreleased/59352-fix-mr-discussion-expansion.yml b/changelogs/unreleased/59352-fix-mr-discussion-expansion.yml
new file mode 100644
index 00000000000..ab9ad53835c
--- /dev/null
+++ b/changelogs/unreleased/59352-fix-mr-discussion-expansion.yml
@@ -0,0 +1,5 @@
+---
+title: Expand resolved discussion when linking to a comment in the discussion
+merge_request: 26483
+author:
+type: fixed
diff --git a/changelogs/unreleased/59441-add-base-domain-to-cluster-api.yml b/changelogs/unreleased/59441-add-base-domain-to-cluster-api.yml
new file mode 100644
index 00000000000..cb5cbba4e92
--- /dev/null
+++ b/changelogs/unreleased/59441-add-base-domain-to-cluster-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add cluster domain to Project Cluster API
+merge_request: 26735
+author:
+type: other
diff --git a/changelogs/unreleased/59502-fix-breadcrumb-artifacts.yml b/changelogs/unreleased/59502-fix-breadcrumb-artifacts.yml
new file mode 100644
index 00000000000..da65c3bc870
--- /dev/null
+++ b/changelogs/unreleased/59502-fix-breadcrumb-artifacts.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes job link in artifacts page breadcrumb
+merge_request: 26592
+author:
+type: fixed
diff --git a/changelogs/unreleased/59546-fix-error-handling-for-missing-domain.yml b/changelogs/unreleased/59546-fix-error-handling-for-missing-domain.yml
new file mode 100644
index 00000000000..8f0ce5d57c5
--- /dev/null
+++ b/changelogs/unreleased/59546-fix-error-handling-for-missing-domain.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Auto DevOps missing domain error handling
+merge_request: 26627
+author:
+type: fixed
diff --git a/changelogs/unreleased/MaxWinterstein-master-patch-23232.yml b/changelogs/unreleased/MaxWinterstein-master-patch-23232.yml
new file mode 100644
index 00000000000..8fb9f1057fe
--- /dev/null
+++ b/changelogs/unreleased/MaxWinterstein-master-patch-23232.yml
@@ -0,0 +1,5 @@
+---
+title: Unify behaviour of 'Copy commit SHA to clipboard' to use full commit SHA.
+merge_request: 25829
+author: Max Winterstein
+type: changed
diff --git a/changelogs/unreleased/check-mergeability-in-merge-to-ref-service.yml b/changelogs/unreleased/check-mergeability-in-merge-to-ref-service.yml
new file mode 100644
index 00000000000..9f615bbb54a
--- /dev/null
+++ b/changelogs/unreleased/check-mergeability-in-merge-to-ref-service.yml
@@ -0,0 +1,5 @@
+---
+title: Check mergeability in MergeToRefService
+merge_request: 26757
+author:
+type: changed
diff --git a/changelogs/unreleased/delete-release-when-delete-tag.yml b/changelogs/unreleased/delete-release-when-delete-tag.yml
new file mode 100644
index 00000000000..58acd449bf1
--- /dev/null
+++ b/changelogs/unreleased/delete-release-when-delete-tag.yml
@@ -0,0 +1,5 @@
+---
+title: Releases will now be automatically deleted when deleting corresponding tag
+merge_request: 26530
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-container-scanning-on-k8s.yml b/changelogs/unreleased/fix-container-scanning-on-k8s.yml
new file mode 100644
index 00000000000..f4500370a0b
--- /dev/null
+++ b/changelogs/unreleased/fix-container-scanning-on-k8s.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Container Scanning in Kubernetes Runners
+merge_request: 26793
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-hidden-statistics.yml b/changelogs/unreleased/fix-hidden-statistics.yml
new file mode 100644
index 00000000000..4d99bd00136
--- /dev/null
+++ b/changelogs/unreleased/fix-hidden-statistics.yml
@@ -0,0 +1,5 @@
+---
+title: Show statistics also when repository is disabled
+merge_request: 26509
+author: Peter Marko
+type: fixed
diff --git a/changelogs/unreleased/fix-routes-n-plus-one-in-user-autocomplete.yml b/changelogs/unreleased/fix-routes-n-plus-one-in-user-autocomplete.yml
new file mode 100644
index 00000000000..ae097e859d9
--- /dev/null
+++ b/changelogs/unreleased/fix-routes-n-plus-one-in-user-autocomplete.yml
@@ -0,0 +1,5 @@
+---
+title: Fix some N+1s in loading routes and counting members for groups in @-autocomplete
+merge_request: 26491
+author:
+type: performance
diff --git a/changelogs/unreleased/fix-transfer-group-possibilities.yml b/changelogs/unreleased/fix-transfer-group-possibilities.yml
new file mode 100644
index 00000000000..ebefb47b3da
--- /dev/null
+++ b/changelogs/unreleased/fix-transfer-group-possibilities.yml
@@ -0,0 +1,5 @@
+---
+title: Fix group transfer selection possibilities
+merge_request: 26123
+author: Peter Marko
+type: fixed
diff --git a/changelogs/unreleased/issue_58547.yml b/changelogs/unreleased/issue_58547.yml
new file mode 100644
index 00000000000..553c752e72d
--- /dev/null
+++ b/changelogs/unreleased/issue_58547.yml
@@ -0,0 +1,5 @@
+---
+title: Add API access check to Graphql
+merge_request: 26570
+author:
+type: other
diff --git a/changelogs/unreleased/knative-update.yml b/changelogs/unreleased/knative-update.yml
new file mode 100644
index 00000000000..e84940ae7e0
--- /dev/null
+++ b/changelogs/unreleased/knative-update.yml
@@ -0,0 +1,5 @@
+---
+title: Knative version bump 0.2.2 -> 0.3.0
+merge_request: 26459
+author: Chris Baumbauer
+type: changed
diff --git a/changelogs/unreleased/kube_helm_auto_devops_213.yml b/changelogs/unreleased/kube_helm_auto_devops_213.yml
new file mode 100644
index 00000000000..3c718d4a22c
--- /dev/null
+++ b/changelogs/unreleased/kube_helm_auto_devops_213.yml
@@ -0,0 +1,5 @@
+---
+title: Bump kubectl to 1.11.9 and Helm to 2.13.1 in Auto-DevOps.gitlab-ci.yml
+merge_request: 26534
+author:
+type: other
diff --git a/changelogs/unreleased/nfriend-css-updates-for-gitlab-design-system-compliance.yml b/changelogs/unreleased/nfriend-css-updates-for-gitlab-design-system-compliance.yml
new file mode 100644
index 00000000000..8cde0958f7a
--- /dev/null
+++ b/changelogs/unreleased/nfriend-css-updates-for-gitlab-design-system-compliance.yml
@@ -0,0 +1,5 @@
+---
+title: Update `border-radius` of form controls and remove extra space above page titles
+merge_request: 24497
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml b/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml
new file mode 100644
index 00000000000..01bd7ede270
--- /dev/null
+++ b/changelogs/unreleased/osw-multi-line-suggestions-creation-strategy.yml
@@ -0,0 +1,5 @@
+---
+title: Implements the creation strategy for multi-line suggestions
+merge_request: 26057
+author:
+type: changed
diff --git a/changelogs/unreleased/persist-fulll-ref-path-for-mr-pipelines.yml b/changelogs/unreleased/persist-fulll-ref-path-for-mr-pipelines.yml
new file mode 100644
index 00000000000..ca42a26e8ff
--- /dev/null
+++ b/changelogs/unreleased/persist-fulll-ref-path-for-mr-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Create MR pipelines with `refs/merge-requests/:iid/head`
+merge_request: 25504
+author:
+type: changed
diff --git a/changelogs/unreleased/recreate-all-diffs-on-import.yml b/changelogs/unreleased/recreate-all-diffs-on-import.yml
new file mode 100644
index 00000000000..fd9124372f3
--- /dev/null
+++ b/changelogs/unreleased/recreate-all-diffs-on-import.yml
@@ -0,0 +1,5 @@
+---
+title: Force to recreate all MR diffs on import
+merge_request: 26480
+author:
+type: fixed
diff --git a/changelogs/unreleased/related-mr-link-cutoff.yml b/changelogs/unreleased/related-mr-link-cutoff.yml
new file mode 100644
index 00000000000..8cf77b6231a
--- /dev/null
+++ b/changelogs/unreleased/related-mr-link-cutoff.yml
@@ -0,0 +1,5 @@
+---
+title: Don't cutoff letters in MR and Issue links
+merge_request: 25910
+author: gfyoung
+type: fixed
diff --git a/changelogs/unreleased/sh-add-gitaly-duration-logs.yml b/changelogs/unreleased/sh-add-gitaly-duration-logs.yml
new file mode 100644
index 00000000000..eea50384278
--- /dev/null
+++ b/changelogs/unreleased/sh-add-gitaly-duration-logs.yml
@@ -0,0 +1,5 @@
+---
+title: Log Gitaly RPC duration to api_json.log and production_json.log
+merge_request: 26652
+author:
+type: other
diff --git a/changelogs/unreleased/sh-clear-pipeline-status-cache-upon-destroy.yml b/changelogs/unreleased/sh-clear-pipeline-status-cache-upon-destroy.yml
new file mode 100644
index 00000000000..55779f0f9d3
--- /dev/null
+++ b/changelogs/unreleased/sh-clear-pipeline-status-cache-upon-destroy.yml
@@ -0,0 +1,5 @@
+---
+title: Clear pipeline status cache after destruction of pipeline
+merge_request: 26575
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-gitaly-find-commit-caching.yml b/changelogs/unreleased/sh-fix-gitaly-find-commit-caching.yml
new file mode 100644
index 00000000000..16d349c407c
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-gitaly-find-commit-caching.yml
@@ -0,0 +1,5 @@
+---
+title: Allow ref name caching CommitService#find_commit
+merge_request: 26248
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-fix-project-branches-merge-status.yml b/changelogs/unreleased/sh-fix-project-branches-merge-status.yml
new file mode 100644
index 00000000000..65f41b3faf9
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-project-branches-merge-status.yml
@@ -0,0 +1,5 @@
+---
+title: Fix API /project/:id/branches not returning correct merge status
+merge_request: 26785
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-force-gc-after-import.yml b/changelogs/unreleased/sh-force-gc-after-import.yml
new file mode 100644
index 00000000000..755d66c1607
--- /dev/null
+++ b/changelogs/unreleased/sh-force-gc-after-import.yml
@@ -0,0 +1,5 @@
+---
+title: Force a full GC after importing a project
+merge_request: 26803
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-optimize-projects-api.yml b/changelogs/unreleased/sh-optimize-projects-api.yml
new file mode 100644
index 00000000000..2f2459be77f
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-projects-api.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize /api/v4/projects endpoint for visibility level
+merge_request: 26481
+author:
+type: performance
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index eba7d2b9fb7..8d9b6624995 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -301,6 +301,10 @@ production: &base
pages_domain_verification_cron_worker:
cron: "*/15 * * * *"
+ # Periodically migrate diffs from the database to external storage
+ schedule_migrate_external_diffs_worker:
+ cron: "15 * * * *"
+
registry:
# enabled: true
# host: registry.example.com
@@ -787,6 +791,10 @@ test:
enabled: true
external_diffs:
enabled: false
+ # Diffs may be `always` external (the default), or they can be made external
+ # after they have become `outdated` (i.e., the MR is closed or a new version
+ # has been pushed).
+ # when: always
# The location where external diffs are stored (default: shared/external-diffs).
# storage_path: shared/external-diffs
object_store:
diff --git a/config/helpers/is_ee_env.js b/config/helpers/is_ee_env.js
new file mode 100644
index 00000000000..1fdbca591c0
--- /dev/null
+++ b/config/helpers/is_ee_env.js
@@ -0,0 +1,9 @@
+const fs = require('fs');
+const path = require('path');
+
+const ROOT_PATH = path.resolve(__dirname, '../..');
+
+module.exports =
+ process.env.EE !== undefined
+ ? JSON.parse(process.env.EE)
+ : fs.existsSync(path.join(ROOT_PATH, 'ee'));
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 99bdf5a95c2..01ffcade931 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -238,6 +238,7 @@ Settings.pages.admin['certificate'] ||= ''
#
Settings['external_diffs'] ||= Settingslogic.new({})
Settings.external_diffs['enabled'] = false if Settings.external_diffs['enabled'].nil?
+Settings.external_diffs['when'] = 'always' if Settings.external_diffs['when'].nil?
Settings.external_diffs['storage_path'] = Settings.absolute(Settings.external_diffs['storage_path'] || File.join(Settings.shared['path'], 'external-diffs'))
Settings.external_diffs['object_store'] = ObjectStoreSettings.parse(Settings.external_diffs['object_store'])
@@ -344,6 +345,10 @@ Settings.cron_jobs['prune_web_hook_logs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['prune_web_hook_logs_worker']['cron'] ||= '0 */1 * * *'
Settings.cron_jobs['prune_web_hook_logs_worker']['job_class'] = 'PruneWebHookLogsWorker'
+Settings.cron_jobs['schedule_migrate_external_diffs_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['schedule_migrate_external_diffs_worker']['cron'] ||= '15 * * * *'
+Settings.cron_jobs['schedule_migrate_external_diffs_worker']['job_class'] = 'ScheduleMigrateExternalDiffsWorker'
+
#
# Sidekiq
#
diff --git a/config/initializers/jira.rb b/config/initializers/jira.rb
new file mode 100644
index 00000000000..05f784a6a2a
--- /dev/null
+++ b/config/initializers/jira.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+# Changes JIRA DVCS user agent requests in order to be successfully handled
+# by our API.
+#
+# Gitlab::Jira::Middleware is only defined on EE
+#
+# Use safe_constantize because the class may exist but has not been loaded yet
+if "Gitlab::Jira::Middleware".safe_constantize
+ Rails.application.config.middleware.use(Gitlab::Jira::Middleware)
+end
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index 164954d1293..5e790a9eccb 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -28,7 +28,12 @@ unless Sidekiq.server?
}
gitaly_calls = Gitlab::GitalyClient.get_request_count
- payload[:gitaly_calls] = gitaly_calls if gitaly_calls > 0
+
+ if gitaly_calls > 0
+ payload[:gitaly_calls] = gitaly_calls
+ payload[:gitaly_duration] = Gitlab::GitalyClient.query_time_ms
+ end
+
payload[:response] = event.payload[:response] if event.payload[:response]
payload[Gitlab::CorrelationId::LOG_KEY] = Gitlab::CorrelationId.current_id
diff --git a/config/karma.config.js b/config/karma.config.js
index c30c58edc6f..7e1e89f3c10 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -110,7 +110,7 @@ module.exports = function(config) {
frameworks: ['jasmine'],
files: [
{ pattern: 'spec/javascripts/test_bundle.js', watched: false },
- { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.html.raw|.png)', included: false },
+ { pattern: 'spec/javascripts/fixtures/**/*@(.json|.html|.png)', included: false },
],
preprocessors: {
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index cef123b86ae..2dc0da00919 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -89,3 +89,4 @@
- [project_daily_statistics, 1]
- [import_issues_csv, 2]
- [chat_notification, 2]
+ - [migrate_external_diffs, 1]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 20b3f4c0264..9a37856a99e 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -1,4 +1,3 @@
-const fs = require('fs');
const path = require('path');
const glob = require('glob');
const webpack = require('webpack');
@@ -12,10 +11,7 @@ const ROOT_PATH = path.resolve(__dirname, '..');
const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
-const IS_EE =
- process.env.EE !== undefined
- ? JSON.parse(process.env.EE)
- : fs.existsSync(path.join(ROOT_PATH, 'ee'));
+const IS_EE = require('./helpers/is_ee_env');
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false';
diff --git a/db/fixtures/development/25_api_personal_access_token.rb b/db/fixtures/development/25_api_personal_access_token.rb
new file mode 100644
index 00000000000..a2e6c674c1f
--- /dev/null
+++ b/db/fixtures/development/25_api_personal_access_token.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require './spec/support/sidekiq'
+
+# Create an api access token for root user with the value: ypCa3Dzb23o5nvsixwPA
+Gitlab::Seeder.quiet do
+ PersonalAccessToken.create!(
+ user_id: User.find_by(username: 'root').id,
+ name: "seeded-api-token",
+ scopes: ["api"],
+ token_digest: "/O0jfLERYT/L5gG8nfByQxqTj43TeLlRzOtJGTzRsbQ="
+ )
+
+ print '.'
+end
diff --git a/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
new file mode 100644
index 00000000000..0048268ca6f
--- /dev/null
+++ b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class AddIndexesForMergeRequestDiffsQuery < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ INDEX_SPECS = [
+ [
+ :merge_request_metrics,
+ :latest_closed_at,
+ { where: 'latest_closed_at IS NOT NULL' }
+ ],
+ [
+ :merge_request_metrics,
+ [:merge_request_id, :merged_at],
+ { where: 'merged_at IS NOT NULL' }
+ ],
+ [
+ :merge_request_diffs,
+ [: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'
+ }
+ ]
+ ].freeze
+
+ disable_ddl_transaction!
+
+ def up
+ INDEX_SPECS.each do |spec|
+ add_concurrent_index(*spec)
+ end
+ end
+
+ def down
+ INDEX_SPECS.reverse.each do |spec|
+ remove_concurrent_index(*spec)
+ end
+ end
+end
diff --git a/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb b/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb
new file mode 100644
index 00000000000..856dfc89fa3
--- /dev/null
+++ b/db/migrate/20190228192410_add_multi_line_attributes_to_suggestion.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddMultiLineAttributesToSuggestion < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :suggestions, :lines_above, :integer, default: 0, allow_null: false
+ add_column_with_default :suggestions, :lines_below, :integer, default: 0, allow_null: false
+ add_column_with_default :suggestions, :outdated, :boolean, default: false, allow_null: false
+ end
+
+ def down
+ remove_columns :suggestions, :outdated, :lines_above, :lines_below
+ end
+end
diff --git a/db/migrate/20190315191339_create_merge_request_assignees_table.rb b/db/migrate/20190315191339_create_merge_request_assignees_table.rb
new file mode 100644
index 00000000000..6fc4463f281
--- /dev/null
+++ b/db/migrate/20190315191339_create_merge_request_assignees_table.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreateMergeRequestAssigneesTable < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ INDEX_NAME = 'index_merge_request_assignees_on_merge_request_id_and_user_id'
+
+ def up
+ create_table :merge_request_assignees do |t|
+ t.references :user, foreign_key: { on_delete: :cascade }, index: true, null: false
+ t.references :merge_request, foreign_key: { on_delete: :cascade }, null: false
+ end
+
+ add_index :merge_request_assignees, [:merge_request_id, :user_id], unique: true, name: INDEX_NAME
+ end
+
+ def down
+ drop_table :merge_request_assignees
+ end
+end
diff --git a/db/migrate/20190325165127_add_managed_to_cluster.rb b/db/migrate/20190325165127_add_managed_to_cluster.rb
new file mode 100644
index 00000000000..e960df9d502
--- /dev/null
+++ b/db/migrate/20190325165127_add_managed_to_cluster.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddManagedToCluster < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:clusters, :managed, :boolean, default: true)
+ end
+
+ def down
+ remove_column(:clusters, :managed)
+ end
+end
diff --git a/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb b/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb
new file mode 100644
index 00000000000..1ecb38e1a86
--- /dev/null
+++ b/db/post_migrate/20190322132835_schedule_populate_merge_request_assignees_table.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class SchedulePopulateMergeRequestAssigneesTable < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 10_000
+ MIGRATION = 'PopulateMergeRequestAssigneesTable'
+ DELAY_INTERVAL = 8.minutes.to_i
+
+ disable_ddl_transaction!
+
+ def up
+ say 'Scheduling `PopulateMergeRequestAssigneesTable` jobs'
+ # We currently have ~4_500_000 merge request records on GitLab.com.
+ # This means it'll schedule ~450 jobs (10k MRs each) with a 8 minutes gap,
+ # so this should take ~60 hours for all background migrations to complete.
+ queue_background_migration_jobs_by_range_at_intervals(MergeRequest, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index eee38651b33..e7dc80c8e72 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190312071108) do
+ActiveRecord::Schema.define(version: 20190325165127) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -670,6 +670,7 @@ ActiveRecord::Schema.define(version: 20190312071108) do
t.string "environment_scope", default: "*", null: false
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
end
@@ -1201,6 +1202,14 @@ ActiveRecord::Schema.define(version: 20190312071108) do
t.index ["user_id"], name: "index_members_on_user_id", using: :btree
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
+ end
+
create_table "merge_request_diff_commits", id: false, force: :cascade do |t|
t.datetime_with_timezone "authored_date"
t.datetime_with_timezone "committed_date"
@@ -1248,6 +1257,7 @@ ActiveRecord::Schema.define(version: 20190312071108) do
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
end
create_table "merge_request_metrics", force: :cascade do |t|
@@ -1263,7 +1273,9 @@ ActiveRecord::Schema.define(version: 20190312071108) do
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
@@ -2032,6 +2044,9 @@ ActiveRecord::Schema.define(version: 20190312071108) do
t.string "commit_id"
t.text "from_content", null: false
t.text "to_content", null: false
+ 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
end
@@ -2455,6 +2470,8 @@ ActiveRecord::Schema.define(version: 20190312071108) do
add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade
add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade
add_foreign_key "members", "users", name: "fk_2e88fb7ce9", on_delete: :cascade
+ add_foreign_key "merge_request_assignees", "merge_requests", on_delete: :cascade
+ add_foreign_key "merge_request_assignees", "users", on_delete: :cascade
add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade
add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade
add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index ecc214d97c8..aead50ea97e 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -222,7 +222,7 @@ The following documentation relates to the DevOps **Verify** stage:
|:---------------------------------------------------|:-----------------------------------------------------------------------------|
| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Integration with GitLab. |
| [JUnit test reports](ci/junit_test_reports.md) | Display JUnit test reports on merge requests. |
-| [Pipeline Graphs](ci/pipelines.md#pipeline-graphs) | Visualize builds. |
+| [Pipeline Graphs](ci/pipelines.md#visualizing-pipelines) | Visualize builds. |
| [Review Apps](ci/review_apps/index.md) | Preview changes to your application right from a merge request. |
<div align="right">
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index 60ad4bf4e8f..28afaf84f5a 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -51,7 +51,7 @@ Hooks can be also placed in `hooks/<hook_name>.d` (global) or
execution of the hooks.
NOTE: **Note:** `<hook_name>.d` would need to be either `pre-receive.d`,
-`post-receive.d`, or `update.d` to work properly. Any other names will be ignored.
+`post-receive.d`, or `update.d` to work properly. Any other names will be ignored.
To look in a different directory for the global custom hooks (those in
`hooks/<hook_name.d>`), set `custom_hooks_dir` in gitlab-shell config. For
@@ -76,9 +76,21 @@ first script exiting with a non-zero value.
> [Introduced][5073] in GitLab 8.10.
-If the commit is declined or an error occurs during the Git hook check,
-the STDERR or STDOUT message of the hook will be present in GitLab's UI.
-STDERR takes precedence over STDOUT.
+To have custom error messages appear in GitLab's UI when the commit is
+declined or an error occurs during the Git hook, your script should:
+
+- Send the custom error messages to either the script's `stdout` or `stderr`.
+- Prefix each message with `GL-HOOK-ERR:` with no characters appearing before the prefix.
+
+### Example custom error message
+
+This hook script written in bash will generate the following message in GitLab's UI:
+
+```bash
+#!/bin/sh
+echo "GL-HOOK-ERR: My custom error message.";
+exit 1
+```
![Custom message from custom Git hook](img/custom_hooks_error_msg.png)
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 2d4b5c65c46..f1cedb85455 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -241,12 +241,24 @@ repository from your GitLab server over HTTP.
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22602) in GitLab 11.8.
-Gitaly supports TLS credentials for GRPC authentication. To be able to communicate
+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)
+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).
+
+Note that 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)
+```
### Example TLS configuration
@@ -303,6 +315,66 @@ certificate_path = '/path/to/cert.pem'
key_path = '/path/to/key.pem'
```
+## Gitaly-ruby
+
+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.
+
+Omnibus:
+
+```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
+```
+
+### 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.
+
+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.
+
+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.
+
+```
+sum(rate(grpc_client_handled_total[5m])) by (grpc_method) > 0
+```
+
## Disabling or enabling the Gitaly service in a cluster environment
If you are running Gitaly [as a remote
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index b1aaa3bca13..f406163aea0 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -71,9 +71,8 @@ bug](https://bugzilla.redhat.com/show_bug.cgi?id=1552203) that may be fixed in
[more recent kernels with this
commit](https://github.com/torvalds/linux/commit/95da1b3a5aded124dd1bda1e3cdb876184813140).
-Users encountering a similar issue may be advised to disable the NFS server
-delegation feature, which is an optimization to reduce the number of network
-round-trips needed to read or write files. To disable NFS server delegations
+GitLab recommends all NFS users disable the NFS server
+delegation feature. To disable NFS server delegations
on an Linux NFS server, do the following:
1. On the NFS server, run:
diff --git a/doc/administration/img/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png
index 845f0de19ce..4f25c471908 100644
--- a/doc/administration/img/custom_hooks_error_msg.png
+++ b/doc/administration/img/custom_hooks_error_msg.png
Binary files differ
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 36dee75bd44..3d40cda491a 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -23,16 +23,19 @@ requests from the API are logged to a separate file in `api_json.log`.
Each line contains a JSON line that can be ingested by Elasticsearch, Splunk, etc. For example:
```json
-{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"queue_duration": 112.47}
+{"method":"GET","path":"/gitlab/gitlab-ce/issues/1234","format":"html","controller":"Projects::IssuesController","action":"show","status":200,"duration":229.03,"view":174.07,"db":13.24,"time":"2017-08-08T20:15:54.821Z","params":[{"key":"param_key","value":"param_value"}],"remote_ip":"18.245.0.1","user_id":1,"username":"admin","gitaly_calls":76,"gitaly_duration":7.41,"queue_duration": 112.47}
```
-In this example, you can see this was a GET request for a specific issue. Notice each line also contains performance data:
+In this example, you can see this was a GET request for a specific
+issue. Notice each line also contains performance data. All times are in
+milliseconds:
-1. `duration`: total time in milliseconds taken to retrieve the request
-1. `queue_duration`: total time in milliseconds that the request was queued inside GitLab Workhorse
+1. `duration`: total time taken to retrieve the request
+1. `queue_duration`: total time that the request was queued inside GitLab Workhorse
1. `view`: total time taken inside the Rails views
1. `db`: total time to retrieve data from the database
1. `gitaly_calls`: total number of calls made to Gitaly
+1. `gitaly_duration`: total time taken by Gitaly calls
User clone/fetch activity using http transport appears in this log as `action: git_upload_pack`.
@@ -85,7 +88,7 @@ Introduced in GitLab 10.0, this file lives in
It helps you see requests made directly to the API. For example:
```json
-{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30}
+{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30,"gitaly_duration":5.36}
```
This entry above shows an access to an internal endpoint to check whether an
diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md
index c34a9519ace..5e9ba4f640f 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -145,3 +145,47 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
```
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+### Alternative in-database storage
+
+Enabling external diffs may reduce the performance of merge requests, as they
+must be retrieved in a separate operation to other data. A compromise may be
+reached by only storing outdated diffs externally, while keeping current diffs
+in the database.
+
+To enable this feature, perform the following steps:
+
+**In Omnibus installations:**
+
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+
+ ```ruby
+ gitlab_rails['external_diffs_when'] = 'outdated'
+ ```
+
+1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
+
+**In installations from source:**
+
+1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
+ lines:
+
+ ```yaml
+ external_diffs:
+ enabled: true
+ when: outdated
+ ```
+
+1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
+
+With this feature enabled, diffs will initially stored in the database, rather
+than externally. They will be moved to external storage once any of these
+conditions become true:
+
+- A newer version of the merge request diff exists
+- The merge request was merged more than seven days ago
+- The merge request was closed more than seven day ago
+
+These rules strike a balance between space and performance by only storing
+frequently-accessed diffs in the database. Diffs that are less likely to be
+accessed are moved to external storage instead.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 288ce376687..17d72b96a51 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -125,7 +125,7 @@ The Pages daemon doesn't listen to the outside world.
pages_external_url 'http://example.io'
```
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
Watch the [video tutorial][video-admin] for this configuration.
@@ -157,7 +157,22 @@ outside world.
where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key,
respectively.
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
+
+### Additional configuration for Docker container
+
+The GitLab Pages daemon will not have permissions to bind mounts when it runs
+in a Docker container. To overcome this issue you'll need to change the chroot
+behavior:
+
+1. Edit `/etc/gitlab/gitlab.rb`.
+1. Set the `inplace_chroot` to `true` for GitLab Pages:
+
+ ```shell
+ gitlab_pages['inplace_chroot'] = true
+ ```
+
+1. [Reconfigure GitLab][reconfigure].
## Advanced configuration
@@ -195,7 +210,7 @@ world. Custom domains are supported, but no TLS.
`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]
+1. [Reconfigure GitLab][reconfigure].
### Custom domains with TLS support
@@ -229,7 +244,7 @@ world. Custom domains and TLS are supported.
`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]
+1. [Reconfigure GitLab][reconfigure].
### Custom domain verification
@@ -287,7 +302,7 @@ Follow the steps below to configure verbose logging of GitLab Pages daemon.
gitlab_pages['log_verbose'] = true
```
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
## Change storage path
@@ -302,7 +317,7 @@ are stored.
gitlab_rails['pages_path'] = "/mnt/storage/pages"
```
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
## Configure listener for reverse proxy requests
@@ -325,7 +340,7 @@ Omnibus GitLab 11.1.
gitlab_pages['listen_proxy'] = "localhost:10080"
```
-1. [Reconfigure GitLab][reconfigure]
+1. [Reconfigure GitLab][reconfigure].
## Set maximum pages size
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index 7f25423171f..1689b0a57d6 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -62,6 +62,8 @@ 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`
respectively.
+NOTE: **Note:** This example uses NFS and CephFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](./high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
+
**For installations from source**
1. Edit `gitlab.yml` and add the storage paths:
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 25c3d564560..4e1e363888d 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -142,7 +142,7 @@ projects:
2. 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][rake/rollback-to-legacy] for instructions.
+[rake task documentation for storage rollback](raketasks/storage.md#rollback-from-hashed-storage-to-legacy-storage) for instructions.
The rollback task also supports specifying a range of Project IDs. Here is an example
of limiting the rollout to Project IDs 50 to 100, in an Omnibus Gitlab installation:
@@ -205,6 +205,5 @@ They are also S3 compatible since **10.0** (GitLab Premium), and available in Gi
[ce-2821]: https://gitlab.com/gitlab-com/infrastructure/issues/2821
[ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283
[rake/migrate-to-hashed]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage
-[rake/rollback-to-legacy]: raketasks/storage.md#rollback
[storage-paths]: repository_storage_types.md
[gitaly]: gitaly/index.md
diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md
index 20d924ab35e..bad7a655d08 100644
--- a/doc/api/build_triggers.md
+++ b/doc/api/build_triggers.md
@@ -1 +1,5 @@
-This document was moved to [Pipeline Triggers](pipeline_triggers.md).
+---
+redirect_to: 'pipeline_triggers.md'
+---
+
+This document was moved to [another location](pipeline_triggers.md).
diff --git a/doc/api/builds.md b/doc/api/builds.md
index a6edda68bc4..0154d35cab6 100644
--- a/doc/api/builds.md
+++ b/doc/api/builds.md
@@ -1 +1,5 @@
+---
+redirect_to: 'jobs.md'
+---
+
This document was moved to [another location](jobs.md).
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 09546fcac3f..c8c282a71d9 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -155,6 +155,32 @@ Example response:
}
```
+GitLab supports [form encoding](../README.md#encoding-api-parameters-of-array-and-hash-types). The following is an example using Commit API with form encoding:
+
+```bash
+curl --request POST \
+ --form "branch=master" \
+ --form "commit_message=some commit message" \
+ --form "start_branch=master" \
+ --form "actions[][action]=create" \
+ --form "actions[][file_path]=foo/bar" \
+ --form "actions[][content]=</path/to/local.file" \
+ --form "actions[][action]=delete" \
+ --form "actions[][file_path]=foo/bar2" \
+ --form "actions[][action]=move" \
+ --form "actions[][file_path]=foo/bar3" \
+ --form "actions[][previous_path]=foo/bar4" \
+ --form "actions[][content]=</path/to/local1.file" \
+ --form "actions[][action]=update" \
+ --form "actions[][file_path]=foo/bar5" \
+ --form "actions[][content]=</path/to/local2.file" \
+ --form "actions[][action]=chmod" \
+ --form "actions[][file_path]=foo/bar5" \
+ --form "actions[][execute_filemode]=true" \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/projects/1/repository/commits"
+```
+
## Get a single commit
Get a specific commit identified by the commit hash or name of a branch or tag.
@@ -196,9 +222,9 @@ Example response:
"last_pipeline" : {
"id": 8,
"ref": "master",
- "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0"
+ "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0",
"status": "created"
- }
+ },
"stats": {
"additions": 15,
"deletions": 10,
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index 1c2f56581eb..260eb09cc38 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -12,6 +12,7 @@ GET /groups/:id/milestones?iids[]=42
GET /groups/:id/milestones?iids[]=42&iids[]=43
GET /groups/:id/milestones?state=active
GET /groups/:id/milestones?state=closed
+GET /groups/:id/milestones?title=1.0
GET /groups/:id/milestones?search=version
```
@@ -22,6 +23,7 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones |
+| `title` | string | optional | Return only the milestones having the given `title` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash
diff --git a/doc/api/milestones.md b/doc/api/milestones.md
index 897184d51af..3b76c19dc07 100644
--- a/doc/api/milestones.md
+++ b/doc/api/milestones.md
@@ -10,6 +10,7 @@ GET /projects/:id/milestones?iids[]=42
GET /projects/:id/milestones?iids[]=42&iids[]=43
GET /projects/:id/milestones?state=active
GET /projects/:id/milestones?state=closed
+GET /projects/:id/milestones?title=1.0
GET /projects/:id/milestones?search=version
```
@@ -20,6 +21,7 @@ Parameters:
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones |
+| `title` | string | optional | Return only the milestones having the given `title` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
index 8efb98fe1fc..02334f0298e 100644
--- a/doc/api/project_clusters.md
+++ b/doc/api/project_clusters.md
@@ -33,6 +33,7 @@ Example response:
{
"id":18,
"name":"cluster-1",
+ "domain":"example.com",
"created_at":"2019-01-02T20:18:12.563Z",
"provider_type":"user",
"platform_type":"kubernetes",
@@ -90,6 +91,7 @@ Example response:
{
"id":18,
"name":"cluster-1",
+ "domain":"example.com",
"created_at":"2019-01-02T20:18:12.563Z",
"provider_type":"user",
"platform_type":"kubernetes",
@@ -157,6 +159,7 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of the project owned by the authenticated user |
| `name` | String | yes | The name of the cluster |
+| `domain` | String | no | The [base domain](../user/project/clusters/index.md#base_domain) of the cluster |
| `enabled` | Boolean | no | Determines if cluster is active or not, 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 |
@@ -247,6 +250,7 @@ Parameters:
| `id` | integer | yes | The ID of the project owned by the authenticated user |
| `cluster_id` | integer | yes | The ID of the cluster |
| `name` | String | no | The name of the cluster |
+| `domain` | String | no | The [base domain](../user/project/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 |
@@ -262,7 +266,7 @@ Example request:
```bash
curl --header 'Private-Token: <your_access_token>' https://gitlab.example.com/api/v4/projects/26/clusters/24 \
-H "Content-Type:application/json" \
--X PUT --data '{"name":"new-cluster-name","api_url":"https://new-api-url.com"}'
+-X PUT --data '{"name":"new-cluster-name","domain":"new-domain.com","api_url":"https://new-api-url.com"}'
```
Example response:
@@ -271,6 +275,7 @@ Example response:
{
"id":24,
"name":"new-cluster-name",
+ "domain":"new-domain.com",
"created_at":"2019-01-03T21:53:40.610Z",
"provider_type":"user",
"platform_type":"kubernetes",
diff --git a/doc/api/services.md b/doc/api/services.md
index 03d0a80aa64..1f84e2de7de 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -522,7 +522,7 @@ Parameters:
| `username` | string | yes | The username of the user created to be used with GitLab/JIRA. |
| `password` | string | yes | The password of the user created to be used with GitLab/JIRA. |
| `active` | boolean | no | Activates or deactivates the service. Defaults to false (deactivated). |
-| `jira_issue_transition_id` | integer | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
+| `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. |
### Delete JIRA service
diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md
index e88d536282a..188989bc94e 100644
--- a/doc/api/suggestions.md
+++ b/doc/api/suggestions.md
@@ -24,8 +24,6 @@ Example response:
```json
{
"id": 36,
- "from_original_line": 10,
- "to_original_line": 10,
"from_line": 10,
"to_line": 10,
"appliable": false,
diff --git a/doc/articles/artifactory_and_gitlab/index.md b/doc/articles/artifactory_and_gitlab/index.md
index 6a590b53727..ed9fd135e7c 100644
--- a/doc/articles/artifactory_and_gitlab/index.md
+++ b/doc/articles/artifactory_and_gitlab/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../ci/examples/artifactory_and_gitlab/index.md'
+---
+
This document was moved to [another location](../../ci/examples/artifactory_and_gitlab/index.md)
diff --git a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
index a8320c12e6b..8e2e54711e7 100644
--- a/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
+++ b/doc/articles/how_to_configure_ldap_gitlab_ce/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md'
+---
+
This document was moved to [another location](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md).
diff --git a/doc/articles/how_to_install_git/index.md b/doc/articles/how_to_install_git/index.md
index 3e6003a33b7..62598101895 100644
--- a/doc/articles/how_to_install_git/index.md
+++ b/doc/articles/how_to_install_git/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../topics/git/how_to_install_git/index.md'
+---
+
This document was moved to [another location](../../topics/git/how_to_install_git/index.md).
diff --git a/doc/articles/laravel_with_gitlab_and_envoy/index.md b/doc/articles/laravel_with_gitlab_and_envoy/index.md
index b092cdb0f7a..fa4f6243410 100644
--- a/doc/articles/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/articles/laravel_with_gitlab_and_envoy/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../ci/examples/laravel_with_gitlab_and_envoy/index.md'
+---
+
This document was moved to [another location](../../ci/examples/laravel_with_gitlab_and_envoy/index.md).
diff --git a/doc/articles/numerous_undo_possibilities_in_git/index.md b/doc/articles/numerous_undo_possibilities_in_git/index.md
index 3f46ee9a5e6..83aac82db4e 100644
--- a/doc/articles/numerous_undo_possibilities_in_git/index.md
+++ b/doc/articles/numerous_undo_possibilities_in_git/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../topics/git/numerous_undo_possibilities_in_git/index.md'
+---
+
This document was moved to [another location](../../topics/git/numerous_undo_possibilities_in_git/index.md).
diff --git a/doc/articles/runner_autoscale_aws/index.md b/doc/articles/runner_autoscale_aws/index.md
index e2667aebc5f..fb769731256 100644
--- a/doc/articles/runner_autoscale_aws/index.md
+++ b/doc/articles/runner_autoscale_aws/index.md
@@ -1 +1,5 @@
+---
+redirect_to: 'https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/index.html'
+---
+
This document was moved to [another location](https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/index.html).
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 47810a8b7b6..913e9d4d789 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -43,28 +43,27 @@ For complete control, you can manually configure GitLab CI/CD.
With basic knowledge of how GitLab CI/CD works, the following documentation extends your knowledge
into more features:
-| Topic | Description |
-|:--------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|
-| [Creating and using CI/CD pipelines](pipelines.md) | Understand, visualize, create, and use CI/CD pipelines. |
-| [CI/CD Variables](variables/README.md) | How environment variables can be configured and made available in pipelines. |
-| [Where variables can be used](variables/where_variables_can_be_used.md) | A deeper look into where and how CI/CD variables can be used. |
-| [User](../user/permissions.md#gitlab-cicd-permissions) and [job](../user/permissions.md#job-permissions) permissions | Learn about the access levels a user can have for performing certain CI actions. |
-| [Configuring GitLab Runners](runners/README.md) | Documentation for configuring [GitLab Runner](https://docs.gitlab.com/runner/). |
-| [Introduction to environments and deployments](environments.md) | Learn how to separate your jobs into environments and use them for different purposes like testing, building and, deploying. |
-| [Job artifacts](../user/project/pipelines/job_artifacts.md) | Learn about the output of jobs. |
-| [Cache dependencies in GitLab CI/CD](caching/index.md) | Discover how to speed up pipelines using caching. |
-| [Using Git submodules with GitLab CI](git_submodules.md) | How to run your CI jobs when using Git submodules. |
-| [Pipelines for merge requests](merge_request_pipelines/index.md) | Create pipelines specifically for merge requests. |
-| [Using SSH keys with GitLab CI/CD](ssh_keys/README.md) | Use SSH keys in your build environment. |
-| [Triggering pipelines through the API](triggers/README.md) | Use the GitLab API to trigger a pipeline. |
-| [Pipeline schedules](../user/project/pipelines/schedules.md) | Trigger pipelines on a schedule. |
-| [Connecting GitLab with a Kubernetes cluster](../user/project/clusters/index.md) | Integrate one or more Kubernetes clusters to your project. |
-| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
-| [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. |
-| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes in a per-branch basis. |
-| [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) **[PREMIUM]** | Check the current health and status of each CI/CD environment running on Kubernetes. |
-| [GitLab CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html) **[PREMIUM]** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and BitBucket Cloud. |
-| [Protected environments](https://docs.gitlab.com/ce/ci/environments/protected_environments.html) **[PREMIUM]** | Ensure that only people with the right privileges can deploy to an environment. |
+| Topic | Description |
+|:--------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------|
+| [Creating and using CI/CD pipelines](pipelines.md) | Understand, visualize, create, and use CI/CD pipelines. |
+| [CI/CD Variables](variables/README.md) | How environment variables can be configured and made available in pipelines. |
+| [Where variables can be used](variables/where_variables_can_be_used.md) | A deeper look into where and how CI/CD variables can be used. |
+| [User](../user/permissions.md#gitlab-cicd-permissions) and [job](../user/permissions.md#job-permissions) permissions | Learn about the access levels a user can have for performing certain CI actions. |
+| [Configuring GitLab Runners](runners/README.md) | Documentation for configuring [GitLab Runner](https://docs.gitlab.com/runner/). |
+| [Environments and deployments](environments.md) | Deploy the output of jobs into environments for reviewing, staging, and production. |
+| [Job artifacts](../user/project/pipelines/job_artifacts.md) | Learn about the output of jobs. |
+| [Cache dependencies in GitLab CI/CD](caching/index.md) | Discover how to speed up pipelines using caching. |
+| [Using Git submodules with GitLab CI](git_submodules.md) | How to run your CI jobs when using Git submodules. |
+| [Pipelines for merge requests](merge_request_pipelines/index.md) | Create pipelines specifically for merge requests. |
+| [Using SSH keys with GitLab CI/CD](ssh_keys/README.md) | Use SSH keys in your build environment. |
+| [Triggering pipelines through the API](triggers/README.md) | Use the GitLab API to trigger a pipeline. |
+| [Pipeline schedules](../user/project/pipelines/schedules.md) | Trigger pipelines on a schedule. |
+| [Connecting GitLab with a Kubernetes cluster](../user/project/clusters/index.md) | Integrate one or more Kubernetes clusters to your project. |
+| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
+| [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. |
+| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes in a per-branch basis. |
+| [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) **[PREMIUM]** | Check the current health and status of each CI/CD environment running on Kubernetes. |
+| [GitLab CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html) **[PREMIUM]** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and BitBucket Cloud. |
### GitLab Pages
@@ -121,7 +120,7 @@ See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJ
As GitLab CI/CD has evolved, certain breaking changes have been necessary. These are:
-- [CI variables renaming for GitLab 9.0](variables/README.md#gitlab-90-renaming). Read about the
+- [CI variables renaming for GitLab 9.0](variables/deprecated_variables.md#gitlab-90-renamed-variables). Read about the
deprecated CI variables and what you should use for GitLab 9.0+.
- [New CI job permissions model](../user/project/new_ci_build_permissions_model.md).
See what changed in GitLab 8.12 and how that affects your jobs.
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index 05d392d54cb..54493bc2922 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -1,45 +1,65 @@
-# Introduction to environments and deployments
+# Environments and deployments
> Introduced in GitLab 8.9.
-During the development of software, there can be many stages until it's ready
-for public consumption. You sure want to first test your code and then deploy it
-in a testing or staging environment before you release it to the public. That
-way you can prevent bugs not only in your software, but in the deployment
-process as well.
+Environments allow control of the continuous deployment of your software,
+all within GitLab.
-GitLab CI is capable of not only testing or building your projects, but also
+## Introduction
+
+There are many stages required in the software development process before the software is ready
+for public consumption.
+
+For example:
+
+1. Develop your code.
+1. Test your code.
+1. Deploy your code into a testing or staging environment before you release it to the public.
+
+This helps prevent bugs not only in your software, but in the deployment process as well.
+
+GitLab CI/CD is capable of not only testing or building your projects, but also
deploying them in your infrastructure, with the added benefit of giving you a
way to track your deployments. In other words, you can always know what is
currently being deployed or has been deployed on your servers.
-## Overview
+It's important to know that:
-With environments, you can control the Continuous Deployment of your software
-all within GitLab. All you need to do is define them in your project's
-[`.gitlab-ci.yml`][yaml] as we will explore below. GitLab provides a full
-history of your deployments per every environment.
+- Environments are like tags for your CI jobs, describing where code gets deployed.
+- Deployments are created when [jobs](yaml/README.md#introduction) deploy versions of code to environments,
+ so every environment can have one or more deployments.
-Environments are like tags for your CI jobs, describing where code gets deployed.
-Deployments are created when [jobs](yaml/README.md#introduction) deploy versions of code to environments,
-so every environment can have one or more deployments. GitLab keeps track of
-your deployments, so you always know what is currently being deployed on your
-servers. If you have a deployment service such as [Kubernetes][kube]
+GitLab:
+
+- Provides a full history of your deployments per every environment.
+- Keeps track of your deployments, so you always know what is currently being deployed on your
+ servers.
+
+If you have a deployment service such as [Kubernetes](../user/project/clusters/index.md)
enabled for your project, you can use it to assist with your deployments, and
can even access a [web terminal](#web-terminals) for your environment from within GitLab!
-To better understand how environments and deployments work, let's consider an
-example. We assume that you have already created a project in GitLab and set up
-a Runner. The example will cover the following:
+## Configuring environments
+
+Configuring environments involves:
+
+1. Understanding how [pipelines](pipelines.md) work.
+1. Defining environments in your project's [`.gitlab-ci.yml`](yaml/README.md) file.
+
+The rest of this section illustrates how to configure environments and deployments using an example.
+It assumes you have already:
-- We are developing an application
-- We want to run tests and build our app on all branches
-- Our default branch is `master`
-- We deploy the app only when a pipeline on `master` branch is run
+- Created a [project](../gitlab-basics/create-project.md) in GitLab.
+- Set up [a Runner](runners/README.md).
-Let's see how it all ties together.
+In the scenario:
-## Defining environments
+- We are developing an application.
+- We want to run tests and build our app on all branches.
+- Our default branch is `master`.
+- We deploy the app only when a pipeline on `master` branch is run.
+
+### Defining environments
Let's consider the following `.gitlab-ci.yml` example:
@@ -70,123 +90,64 @@ deploy_staging:
We have defined 3 [stages](yaml/README.md#stages):
-- test
-- build
-- deploy
+- `test`
+- `build`
+- `deploy`
The jobs assigned to these stages will run in this order. If a job fails, then
the jobs that are assigned to the next stage won't run, rendering the pipeline
-as failed. In our case, the `test` job will run first, then the `build` and
-lastly the `deploy_staging`. With this, we ensure that first the tests pass,
-then our app is able to be built successfully, and lastly we deploy to the
-staging server.
+as failed.
+
+In our case:
+
+- The `test` job will run first.
+- Then the `build` job.
+- Lastly the `deploy_staging` job.
+
+With this configuration, we ensure that:
+
+- The tests pass.
+- Our app is able to be built successfully.
+- Lastly we deploy to the staging server.
+NOTE: **Note:**
The `environment` keyword is just a hint for GitLab that this job actually
-deploys to this environment's `name`. It can also have a `url` which, as we
-will later see, is exposed in various places within GitLab. Each time a job that
-has an environment specified and succeeds, a deployment is recorded, remembering
+deploys to this environment's `name`. It can also have a `url` that is
+exposed in various places within GitLab. Each time a job that
+has an environment specified succeeds, a deployment is recorded, storing
the Git SHA and environment name.
-> **Note:**
+In summary, with the above `.gitlab-ci.yml` we have achieved the following:
+
+- All branches will run the `test` and `build` jobs.
+- The `deploy_staging` job will run [only](yaml/README.md#onlyexcept-basic) on the `master`
+ branch, which means all merge requests that are created from branches don't
+ get deployed to the staging server.
+- When a merge request is merged, all jobs will run and the `deploy_staging`
+ job will deploy our code to a staging server while the deployment
+ will be recorded in an environment named `staging`.
+
> Starting with GitLab 8.15, the environment name is exposed to the Runner in
> two forms: `$CI_ENVIRONMENT_NAME`, and `$CI_ENVIRONMENT_SLUG`. The first is
> the name given in `.gitlab-ci.yml` (with any variables expanded), while the
> second is a "cleaned-up" version of the name, suitable for use in URLs, DNS,
> etc.
->
-> **Note:**
+
> Starting with GitLab 9.3, the environment URL is exposed to the Runner via
> `$CI_ENVIRONMENT_URL`. The URL would be expanded from `.gitlab-ci.yml`, or if
> the URL was not defined there, the external URL from the environment would be
> used.
-To sum up, with the above `.gitlab-ci.yml` we have achieved that:
-
-- All branches will run the `test` and `build` jobs.
-- The `deploy_staging` job will run [only](yaml/README.md#onlyexcept-basic) on the `master`
- branch which means all merge requests that are created from branches don't
- get to deploy to the staging server
-- When a merge request is merged, all jobs will run and the `deploy_staging`
- in particular will deploy our code to a staging server while the deployment
- will be recorded in an environment named `staging`.
-
-Let's now see how that information is exposed within GitLab.
-
-## Viewing the current status of an environment
-
-The environment list under your project's **Operations > Environments**, is
-where you can find information of the last deployment status of an environment.
-
-Here's how the Environments page looks so far.
-
-![Environment view](img/environments_available.png)
-
-There's a bunch of information there, specifically you can see:
-
-- The environment's name with a link to its deployments
-- The last deployment ID number and who performed it
-- The job ID of the last deployment with its respective job name
-- The commit information of the last deployment such as who committed, to what
- branch and the Git SHA of the commit
-- The exact time the last deployment was performed
-- A button that takes you to the URL that you have defined under the
- `environment` keyword in `.gitlab-ci.yml`
-- A button that re-deploys the latest deployment, meaning it runs the job
- defined by the environment name for that specific commit
-
-> **Notes:**
->
-> - While you can create environments manually in the web interface, we recommend
-> that you define your environments in `.gitlab-ci.yml` first. They will
-> be automatically created for you after the first deploy.
-> - The environments page can only be viewed by Reporters and above. For more
-> information on the permissions, see the [permissions documentation][permissions].
-> - Only deploys that happen after your `.gitlab-ci.yml` is properly configured
-> will show up in the "Environment" and "Last deployment" lists.
-
-The information shown in the Environments page is limited to the latest
-deployments, but as you may have guessed an environment can have multiple
-deployments.
-
-## Viewing the deployment history of an environment
-
-GitLab keeps track of your deployments, so you always know what is currently
-being deployed on your servers. That way you can have the full history of your
-deployments per every environment right in your browser. Clicking on an
-environment will show the history of its deployments. Assuming you have deployed
-multiple times already, here's how a specific environment's page looks like.
-
-![Deployments](img/deployments_view.png)
-
-We can see the same information as when in the Environments page, but this time
-all deployments are shown. As you may have noticed, apart from the **Re-deploy**
-button there are now **Rollback** buttons for each deployment. Let's see how
-that works.
-
-## Rolling back changes
-
-You can't control everything, so sometimes things go wrong. When that unfortunate
-time comes GitLab has you covered. Simply by clicking the **Rollback** button
-that can be found in the deployments page
-(**Operations > Environments > `environment name`**) you can relaunch the
-job with the commit associated with it.
-
->**Note:**
-Bear in mind that your mileage will vary and it's entirely up to how you define
-the deployment process in the job's `script` whether the rollback succeeds or not.
-GitLab CI is just following orders.
+### Configuring manual deployments
-Thankfully that was the staging server that we had to rollback, and since we
-learn from our mistakes, we decided to not make the same again when we deploy
-to the production server. Enter manual actions for deployments.
+Converting automatically executed job into jobs requiring to a manual action involves
+adding `when: manual` to the job's configuration.
-## Manually deploying to environments
+To expand on the [previous example](#defining-environments), the following includes
+another job that deploys our app to a production server and is
+tracked by a `production` environment.
-Turning a job from running automatically to a manual action is as simple as
-adding `when: manual` to it. To expand on our previous example, let's add
-another job that this time deploys our app to a production server and is
-tracked by a `production` environment. The `.gitlab-ci.yml` looks like this
-so far:
+The `.gitlab-ci.yml` file for this is as follows:
```yaml
stages:
@@ -224,41 +185,62 @@ deploy_prod:
- master
```
-The `when: manual` action exposes a play button in GitLab's UI and the
-`deploy_prod` job will only be triggered if and when we click that play button.
-You can find it in the pipeline, job, environment, and deployment views.
+The `when: manual` action:
-| Pipelines | Single pipeline | Environments | Deployments | jobs |
-| --------- | ----------------| ------------ | ----------- | -------|
-| ![Pipelines manual action](img/environments_manual_action_pipelines.png) | ![Pipelines manual action](img/environments_manual_action_single_pipeline.png) | ![Environments manual action](img/environments_manual_action_environments.png) | ![Deployments manual action](img/environments_manual_action_deployments.png) | ![Builds manual action](img/environments_manual_action_jobs.png) |
+- Exposes a "play" button in GitLab's UI.
+- Means the `deploy_prod` job will only be triggered when the "play" button is clicked.
-Clicking on the play button in either of these places will trigger the
-`deploy_prod` job, and the deployment will be recorded under a new
+You can find the "play" button in the pipelines, environments, deployments, and jobs views.
+
+| View | Screenshot |
+|:----------------|:-------------------------------------------------------------------------------|
+| Pipelines | ![Pipelines manual action](img/environments_manual_action_pipelines.png) |
+| Single pipeline | ![Pipelines manual action](img/environments_manual_action_single_pipeline.png) |
+| Environments | ![Environments manual action](img/environments_manual_action_environments.png) |
+| Deployments | ![Deployments manual action](img/environments_manual_action_deployments.png) |
+| Jobs | ![Builds manual action](img/environments_manual_action_jobs.png) |
+
+Clicking on the play button in any view will trigger the `deploy_prod` job, and the deployment will be recorded under a new
environment named `production`.
->**Note:**
-Remember that if your environment's name is `production` (all lowercase), then
+NOTE: **Note:**
+If your environment's name is `production` (all lowercase),
it will get recorded in [Cycle Analytics](../user/project/cycle_analytics.md).
-Double the benefit!
-## Dynamic environments
+### Configuring dynamic environments
-As the name suggests, it is possible to create environments on the fly by just
-declaring their names dynamically in `.gitlab-ci.yml`. Dynamic environments is
-the basis of [Review apps](review_apps/index.md).
+Other environments are good for deploying to stable environments like staging or production.
-NOTE: **Note:**
-The `name` and `url` parameters can use most of the CI/CD variables,
-including [predefined](variables/README.md#predefined-environment-variables),
-[project/group ones](variables/README.md#variables) and
-[`.gitlab-ci.yml` variables](yaml/README.md#variables). You however cannot use variables
-defined under `script` or on the Runner's side. There are also other variables that
-are unsupported in the context of `environment:name`. You can read more about
-[where variables can be used](variables/where_variables_can_be_used.md).
-
-GitLab Runner exposes various [environment variables][variables] when a job runs,
-and as such, you can use them as environment names. Let's add another job in
-our example which will deploy to all branches except `master`:
+However, what about environments for branches other than `master`? Dynamic environments can be used to achieve these.
+
+Dynamic environments make it possible to create environments on the fly by
+declaring their names dynamically in `.gitlab-ci.yml`.
+
+Dynamic environments form the basis of [Review apps](review_apps/index.md).
+
+#### Allowed variables
+
+The `name` and `url` parameters for dynamic environments can use most available CI/CD variables,
+including:
+
+- [Predefined environment variables](variables/README.md#predefined-environment-variables)
+- [Project and group variables](variables/README.md#variables)
+- [`.gitlab-ci.yml` variables](yaml/README.md#variables)
+
+However, you cannot use variables defined:
+
+- Under `script`.
+- On the Runner's side.
+
+There are also other variables that are unsupported in the context of `environment:name`.
+For more information, see [Where variables can be used](variables/where_variables_can_be_used.md).
+
+#### Example configuration
+
+GitLab Runner exposes various [environment variables](variables/README.md) when a job runs and so
+you can use them as environment names.
+
+In the following example, a job will deploy to all branches except `master`:
```yaml
deploy_review:
@@ -274,39 +256,49 @@ deploy_review:
- master
```
-Let's break it down in pieces. The job's name is `deploy_review` and it runs
-on the `deploy` stage. The `script` at this point is fictional, you'd have to
-use your own based on your deployment. Then, we set the `environment` with the
-`environment:name` being `review/$CI_COMMIT_REF_NAME`. Now that's an interesting
-one. Since the [environment name][env-name] can contain slashes (`/`), we can
-use this pattern to distinguish between dynamic environments and the regular
-ones.
-
-So, the first part is `review`, followed by a `/` and then `$CI_COMMIT_REF_NAME`
-which takes the value of the branch name. Since `$CI_COMMIT_REF_NAME` itself may
-also contain `/`, or other characters that would be invalid in a domain name or
-URL, we use `$CI_ENVIRONMENT_SLUG` in the `environment:url` so that the
-environment can get a specific and distinct URL for each branch. In this case,
-given a `$CI_COMMIT_REF_NAME` of `100-Do-The-Thing`, the URL will be something
-like `https://100-do-the-4f99a2.example.com`. Again, the way you set up
-the web server to serve these requests is based on your setup.
-
-You could also use `$CI_COMMIT_REF_SLUG` in `environment:url`, e.g.:
-`https://$CI_COMMIT_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG`
-here because it is guaranteed to be unique, but if you're using a workflow like
-[GitLab Flow][gitlab-flow], collisions are very unlikely, and you may prefer
-environment names to be more closely based on the branch name - the example
-above would give you an URL like `https://100-do-the-thing.example.com`
-
-Last but not least, we tell the job to run [`only`](yaml/README.md#onlyexcept-basic) on branches
-[`except`](yaml/README.md#onlyexcept-basic) master.
-
->**Note:**
+In this example:
+
+- The job's name is `deploy_review` and it runs on the `deploy` stage.
+- We set the `environment` with the `environment:name` as `review/$CI_COMMIT_REF_NAME`.
+ Since the [environment name](yaml/README.md#environmentname) can contain slashes (`/`), we can
+ use this pattern to distinguish between dynamic environments and the regular ones.
+- We tell the job to run [`only`](yaml/README.md#onlyexcept-basic) on branches [`except`](yaml/README.md#onlyexcept-basic) `master`.
+
+For the value of:
+
+- `environment:name`, the first part is `review`, followed by a `/` and then `$CI_COMMIT_REF_NAME`,
+ which takes the value of the branch name.
+- `environment:url`, since `$CI_COMMIT_REF_NAME` itself may also contain `/`, or other characters that
+ would be invalid in a domain name or URL, we use `$CI_ENVIRONMENT_SLUG` so that the environment can get a specific and distinct URL for each branch.
+
+ For example, given a `$CI_COMMIT_REF_NAME` of `100-Do-The-Thing`, the URL will be something
+ like `https://100-do-the-4f99a2.example.com`. Again, the way you set up
+ the web server to serve these requests is based on your setup.
+
+ You could also use `$CI_COMMIT_REF_SLUG` in `environment:url`. For example, `https://$CI_COMMIT_REF_SLUG.example.com`.
+ We have used `$CI_ENVIRONMENT_SLUG` here because it is guaranteed to be unique. If you're using a workflow like
+ [GitLab Flow](../workflow/gitlab_flow.md), collisions are unlikely and you may prefer environment names to be more closely based on the branch name. The example
+ above would give you an URL like `https://100-do-the-thing.example.com`.
+
+NOTE: **Note:**
You are not bound to use the same prefix or only slashes in the dynamic
-environments' names (`/`), but as we will see later, this will enable the
-[grouping similar environments](#grouping-similar-environments) feature.
+environments' names (`/`). However, this will enable the [grouping similar environments](#grouping-similar-environments) feature.
+
+### Complete example
+
+The configuration in this section provides a full development workflow where your app is:
-The whole `.gitlab-ci.yml` looks like this so far:
+- Tested.
+- Built.
+- Deployed as a Review App.
+- Deployed to a staging server once the merge request is merged.
+- Finally, manually deployed to the production server.
+
+The following combines the previous configuration examples, including:
+
+- Defining [simple environments](#defining-environments) for testing, building, and deployment to staging.
+- Adding [manual actions](#configuring-manual-deployments) for deployment to production.
+- Creating [dynamic environments](#configuring-dynamic-environments) for deployments for reviewing.
```yaml
stages:
@@ -357,8 +349,9 @@ deploy_prod:
```
A more realistic example would include copying files to a location where a
-webserver (NGINX) could then read and serve. The example below will copy the
-`public` directory to `/srv/nginx/$CI_COMMIT_REF_SLUG/public`:
+webserver (for example, NGINX) could then read and serve.
+
+The example below will copy the `public` directory to `/srv/nginx/$CI_COMMIT_REF_SLUG/public`:
```yaml
review_app:
@@ -370,76 +363,155 @@ review_app:
url: https://$CI_COMMIT_REF_SLUG.example.com
```
-It is assumed that the user has already set up NGINX and GitLab Runner in the
-server this job will run on.
+This example requires that NGINX and GitLab Runner are set up on the server this job will run on.
+
+NOTE: **Note:**
+See the [limitations](#limitations) section for some edge cases regarding naming of your branches and Review Apps.
+
+The complete example provides the following workflow for developers:
+
+- Create a branch locally.
+- Make changes and commit them
+- Push the branch to GitLab.
+- Create a merge request.
+
+Behind the scenes, GitLab runner will:
+
+- Pick up the changes and start running the jobs.
+- Run the jobs sequentially as defined in `stages`:
+ - First, run the tests.
+ - If the tests succeed, build the app.
+ - If the build succeeds, the app will be is deployed to an environment with a name specific to the
+ branch.
+
+So now, every branch:
+
+- Gets its own environment.
+- Is deployed to its own location, with the added benefit of:
+ - Having a [history of deployments](#viewing-deployment-history).
+ - Being able to [rollback changes](#retrying-and-rolling-back) if needed.
+
+For more information on using the URL, see [Using the environment URL](#using-the-environment-url).
+
+### Protected environments
+
+Environments can be "protected", restricting access to them.
+
+For more information, see [Protected environments](environments/protected_environments.md).
+
+## Working with environments
+
+Having configured environments, GitLab provides many features to work with them. These are documented below.
+
+### Viewing environments and deployments
+
+A list of environments and deployment statuses is available on project's **Operations > Environments** page.
+
+For example:
+
+![Environment view](img/environments_available.png)
+
+This example shows:
+
+- The environment's name with a link to its deployments.
+- The last deployment ID number and who performed it.
+- The job ID of the last deployment with its respective job name.
+- The commit information of the last deployment such as who committed, to what
+ branch, and the Git SHA of the commit.
+- The exact time the last deployment was performed.
+- A button that takes you to the URL that you have defined under the
+ `environment` keyword in `.gitlab-ci.yml`.
+- A button that re-deploys the latest deployment, meaning it runs the job
+ defined by the environment name for that specific commit.
+
+The information shown in the **Environments** page is limited to the latest
+deployments, but an environment can have multiple deployments.
+
+> **Notes:**
+>
+> - While you can create environments manually in the web interface, we recommend
+> that you define your environments in `.gitlab-ci.yml` first. They will
+> be automatically created for you after the first deploy.
+> - The environments page can only be viewed by Reporters and above. For more
+> information on the permissions, see the [permissions documentation](../user/permissions.md).
+> - Only deploys that happen after your `.gitlab-ci.yml` is properly configured
+> will show up in the **Environment** and **Last deployment** lists.
+
+### Viewing deployment history
+
+GitLab keeps track of your deployments, so you:
->**Note:**
-Be sure to check out the [limitations](#limitations) section for some edge
-cases regarding naming of your branches and Review Apps.
+- Always know what is currently being deployed on your servers.
+- Can have the full history of your deployments per every environment.
----
+Clicking on an environment shows the history of its deployments. Here's an example **Environments** page
+with multiple deployments:
-The development workflow would now be:
+![Deployments](img/deployments_view.png)
+
+This view is similar to the **Environments** page, but all deployments are shown. Also in this view
+is a **Rollback** button. For more information, see [Retrying and rolling back](#retrying-and-rolling-back).
-- Developer creates a branch locally
-- Developer makes changes, commits and pushes the branch to GitLab
-- Developer creates a merge request
+### Retrying and rolling back
-Behind the scenes:
+If there is a problem with a deployment, you can retry it or roll it back.
-- GitLab Runner picks up the changes and starts running the jobs
-- The jobs run sequentially as defined in `stages`
- - First, the tests pass
- - Then, the job begins and successfully also passes
- - Lastly, the app is deployed to an environment with a name specific to the
- branch
+To retry or rollback a deployment:
-So now, every branch gets its own environment and is deployed to its own place
-with the added benefit of having a [history of deployments](#viewing-the-deployment-history-of-an-environment)
-and also being able to [rollback changes](#rolling-back-changes) if needed.
-Let's briefly see where URL that's defined in the environments is exposed.
+1. Navigate to **Operations > Environments**.
+1. Click on the environment.
+1. On the page that lists the deployment history for the environment, click the:
+ - **Rollback** button against a previously successful deployment, to roll back to that deployment.
+ - **Retry** button against the last deployment, to retry that deployment.
-## Making use of the environment URL
+NOTE: **Note:**
+The defined deployment process in the job's `script` determines whether the rollback succeeds or not.
+
+### Using the environment URL
The [environment URL](yaml/README.md#environmenturl) is exposed in a few
places within GitLab.
-| In a merge request widget as a link | In the Environments view as a button | In the Deployments view as a button |
-| -------------------- | ------------ | ----------- |
-| ![Environment URL in merge request](img/environments_mr_review_app.png) | ![Environment URL in environments](img/environments_available.png) | ![Environment URL in deployments](img/deployments_view.png) |
+These are:
+
+- In a merge request widget as a link:
+ ![Environment URL in merge request](img/environments_mr_review_app.png)
+- In the Environments view as a button:
+ ![Environment URL in environments](img/environments_available.png)
+- In the Deployments view as a button:
+ ![Environment URL in deployments](img/deployments_view.png)
-If a merge request is eventually merged to the default branch (in our case
-`master`) and that branch also deploys to an environment (in our case `staging`
-and/or `production`) you can see this information in the merge request itself.
+You can see this information in a merge request itself if:
+
+- The merge request is eventually merged to the default branch (usually `master`).
+- That branch also deploys to an environment (for example, `staging` or `production`).
+
+For example:
![Environment URLs in merge request](img/environments_link_url_mr.png)
-### Go directly from source files to public pages on the environment
+#### Going from source files to public pages
With GitLab's [Route Maps](review_apps/index.md#route-maps) you can go directly
from source files to public pages on the environment set for Review Apps.
-From then on, you have a full development cycle, where your app is tested, built, deployed
-as a Review App, deployed to a staging server once the merge request is merged,
-and finally manually deployed to the production server. This is a simple workflow,
-but when you have multiple developers working on a project
-at the same time, each of them pushing to their own branches, dynamic environments are
-created all the time. In which case, you probably want to do some clean up. Read
-next how environments can be stopped.
+### Stopping an environment
-## Stopping an environment
+Stopping an environment:
-By stopping an environment, you are effectively terminating its recording of the
-deployments that happen in it.
+- Moves it from the list of **Available** environments to the list of **Stopped** environments on the [**Environments** page](#viewing-environments-and-deployments).
+- Executes an [`on_stop` action](yaml/README.md#environmenton_stop), if defined.
-A branch is associated with an environment when the CI pipeline that is created
-for this branch, was recently deployed to this environment. You can think of
-the CI pipeline as the glue between the branch and the environment:
-`branch ➔ CI pipeline ➔ environment`.
+This is often used when multiple developers are working on a project at the same time,
+each of them pushing to their own branches, causing many dynamic environments to be created.
-There is a special case where environments can be manually stopped. That can
-happen if you provide another job for that matter. The syntax is a little
-tricky since a job calls another job to do the job.
+NOTE: **Note:**
+Starting with GitLab 8.14, dynamic environments will be stopped automatically
+when their associated branch is deleted.
+
+#### Automatically stopping an environment
+
+Environments can be stopped automatically using special configuration.
Consider the following example where the `deploy_review` calls the `stop_review`
to clean up and stop the environment:
@@ -470,35 +542,31 @@ stop_review:
action: stop
```
-Setting the [`GIT_STRATEGY`][git-strategy] to `none` is necessary on the
-`stop_review` job so that the [GitLab Runner] won't try to checkout the code
+Setting the [`GIT_STRATEGY`](yaml/README.md#git-strategy) to `none` is necessary on the
+`stop_review` job so that the [GitLab Runner](https://docs.gitlab.com/runner/) won't try to check out the code
after the branch is deleted.
->**Note:**
-Starting with GitLab 8.14, dynamic environments will be stopped automatically
-when their associated branch is deleted.
-
When you have an environment that has a stop action defined (typically when
-the environment describes a review app), GitLab will automatically trigger a
+the environment describes a Review App), GitLab will automatically trigger a
stop action when the associated branch is deleted. The `stop_review` job must
be in the same `stage` as the `deploy_review` one in order for the environment
to automatically stop.
-You can read more in the [`.gitlab-ci.yml` reference][onstop].
+You can read more in the [`.gitlab-ci.yml` reference](yaml/README.md#environmenton_stop).
-## Grouping similar environments
+### Grouping similar environments
-> [Introduced][ce-7015] in GitLab 8.14.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7015) in GitLab 8.14.
-As we've seen in the [dynamic environments](#dynamic-environments), you can
-prepend their name with a word, then followed by a `/` and finally the branch
-name which is automatically defined by the `CI_COMMIT_REF_NAME` variable.
+As documented in [Configuring dynamic environments](#configuring-dynamic-environments), you can
+prepend environment name with a word, followed by a `/`, and finally the branch
+name, which is automatically defined by the `CI_COMMIT_REF_NAME` variable.
In short, environments that are named like `type/foo` are presented under a
group named `type`.
-In our minimal example, we name the environments `review/$CI_COMMIT_REF_NAME`
-where `$CI_COMMIT_REF_NAME` is the branch name:
+In our [minimal example](#example-configuration), we named the environments `review/$CI_COMMIT_REF_NAME`
+where `$CI_COMMIT_REF_NAME` is the branch name. Here is a snippet of the example:
```yaml
deploy_review:
@@ -509,49 +577,47 @@ deploy_review:
name: review/$CI_COMMIT_REF_NAME
```
-In that case, if you visit the Environments page, and provided the branches
+In this case, if you visit the **Environments** page and the branches
exist, you should see something like:
![Environment groups](img/environments_dynamic_groups.png)
-## Monitoring environments
+### Monitoring environments
> **Notes:**
>
> - For the monitoring dashboard to appear, you need to:
-> - Have enabled the [Prometheus integration][prom]
-> - Configured Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/index.md)
-> - With GitLab 9.2, all deployments to an environment are shown directly on the
-> monitoring dashboard
+> - Enable the [Prometheus integration](../user/project/integrations/prometheus.md).
+> - Configure Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/index.md)
+> - With GitLab 9.2, all deployments to an environment are shown directly on the monitoring dashboard.
-If you have enabled [Prometheus for monitoring system and response metrics](https://docs.gitlab.com/ee/user/project/integrations/prometheus.html), you can monitor the performance behavior of your app running in each environment.
+If you have enabled [Prometheus for monitoring system and response metrics](../user/project/integrations/prometheus.md), you can monitor the performance behavior of your app running in each environment.
-Once configured, GitLab will attempt to retrieve [supported performance metrics](https://docs.gitlab.com/ee/user/project/integrations/prometheus_library/index.html) for any
-environment which has had a successful deployment. If monitoring data was
-successfully retrieved, a Monitoring button will appear for each environment.
+Once configured, GitLab will attempt to retrieve [supported performance metrics](../user/project/integrations/prometheus_library/index.md) for any
+environment that has had a successful deployment. If monitoring data was
+successfully retrieved, a **Monitoring** button will appear for each environment.
![Environment Detail with Metrics](img/deployments_view.png)
-Clicking on the Monitoring button will display a new page, showing up to the last
+Clicking on the **Monitoring** button will display a new page showing up to the last
8 hours of performance data. It may take a minute or two for data to appear
after initial deployment.
-All deployments to an environment are shown directly on the monitoring dashboard
+All deployments to an environment are shown directly on the monitoring dashboard,
which allows easy correlation between any changes in performance and a new
version of the app, all without leaving GitLab.
![Monitoring dashboard](img/environments_monitoring.png)
-## Web terminals
+### Web terminals
+
+> Web terminals were added in GitLab 8.15 and are only available to project Maintainers and Owners.
->**Note:**
-Web terminals were added in GitLab 8.15 and are only available to project
-maintainers and owners.
+If you deploy to your environments with the help of a deployment service (for example,
+the [Kubernetes integration](../user/project/clusters/index.md)), GitLab can open
+a terminal session to your environment.
-If you deploy to your environments with the help of a deployment service (e.g.,
-the [Kubernetes integration][kube]), GitLab can open
-a terminal session to your environment! This is a very powerful feature that
-allows you to debug issues without leaving the comfort of your web browser. To
+This is a powerful feature that allows you to debug issues without leaving the comfort of your web browser. To
enable it, just follow the instructions given in the service integration
documentation.
@@ -568,41 +634,71 @@ establish the terminal session:
![Terminal page](img/environments_terminal_page.png)
-This works just like any other terminal - you'll be in the container created
-by your deployment, so you can run shell commands and get responses in real
-time, check the logs, try out configuration or code tweaks, etc. You can open
-multiple terminals to the same environment - they each get their own shell
-session - and even a multiplexer like `screen` or `tmux`!
+This works just like any other terminal. You'll be in the container created
+by your deployment so you can:
->**Note:**
-Container-based deployments often lack basic tools (like an editor), and may
-be stopped or restarted at any time. If this happens, you will lose all your
-changes! Treat this as a debugging tool, not a comprehensive online IDE.
+- Run shell commands and get responses in real time.
+- Check the logs.
+- Try out configuration or code tweaks etc.
----
+You can open multiple terminals to the same environment, they each get their own shell
+session and even a multiplexer like `screen` or `tmux`.
-While this is fine for deploying to some stable environments like staging or
-production, what happens for branches? So far we haven't defined anything
-regarding deployments for branches other than `master`. Dynamic environments
-will help us achieve that.
+NOTE: **Note:**
+Container-based deployments often lack basic tools (like an editor), and may
+be stopped or restarted at any time. If this happens, you will lose all your
+changes. Treat this as a debugging tool, not a comprehensive online IDE.
-## Checkout deployments locally
+### Check out deployments locally
-Since 8.13, a reference in the git repository is saved for each deployment, so
+Since GitLab 8.13, a reference in the Git repository is saved for each deployment, so
knowing the state of your current environments is only a `git fetch` away.
-In your git config, append the `[remote "<your-remote>"]` block with an extra
+In your Git configuration, append the `[remote "<your-remote>"]` block with an extra
fetch line:
-```
+```text
fetch = +refs/environments/*:refs/remotes/origin/environments/*
```
+### Scoping environments with specs **[PREMIUM]**
+
+Some GitLab [Enterprise Edition](https://about.gitlab.com/pricing/) features can behave differently for each
+environment. For example, you can [create a secret variable to be injected only into a production environment](variables/README.md#limiting-environment-scopes-of-variables-premium).
+
+In most cases, these features use the _environment specs_ mechanism, which offers
+an efficient way to implement scoping within each environment group.
+
+Let's say there are four environments:
+
+- `production`
+- `staging`
+- `review/feature-1`
+- `review/feature-2`
+
+Each environment can be matched with the following environment spec:
+
+| Environment Spec | `production` | `staging` | `review/feature-1` | `review/feature-2` |
+|:-----------------|:-------------|:----------|:-------------------|:-------------------|
+| * | Matched | Matched | Matched | Matched |
+| production | Matched | | | |
+| staging | | Matched | | |
+| review/* | | | Matched | Matched |
+| review/feature-1 | | | Matched | |
+
+As you can see, you can use specific matching for selecting a particular environment,
+and also use wildcard matching (`*`) for selecting a particular environment group,
+such as [Review apps](review_apps/index.md) (`review/*`).
+
+NOTE: **Note:**
+The most _specific_ spec takes precedence over the other wildcard matching.
+In this case, `review/feature-1` spec takes precedence over `review/*` and `*` specs.
+
## Limitations
-1. You are limited to use only the [CI predefined variables][variables] in the
- `environment: name`. If you try to re-use variables defined inside `script`
- as part of the environment name, it will not work.
+You are limited to use only the [CI predefined variables](variables/README.md) in the
+`environment: name`. If you try to re-use variables defined inside `script`
+as part of the environment name, it will not work.
## Further reading
@@ -611,18 +707,3 @@ Below are some links you may find interesting:
- [The `.gitlab-ci.yml` definition of environments](yaml/README.md#environment)
- [A blog post on Deployments & Environments](https://about.gitlab.com/2016/08/26/ci-deployment-and-environments/)
- [Review Apps - Use dynamic environments to deploy your code for every branch](review_apps/index.md)
-
-[Pipelines]: pipelines.md
-[yaml]: yaml/README.md
-[environments]: #environments
-[deployments]: #deployments
-[permissions]: ../user/permissions.md
-[variables]: variables/README.md
-[env-name]: yaml/README.md#environmentname
-[onstop]: yaml/README.md#environmenton_stop
-[ce-7015]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7015
-[gitlab-flow]: ../workflow/gitlab_flow.md
-[gitlab runner]: https://docs.gitlab.com/runner/
-[git-strategy]: yaml/README.md#git-strategy
-[kube]: ../user/project/clusters/index.md
-[prom]: ../user/project/integrations/prometheus.md
diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md
new file mode 100644
index 00000000000..219af4ced9d
--- /dev/null
+++ b/doc/ci/environments/protected_environments.md
@@ -0,0 +1,48 @@
+# Protected Environments **[PREMIUM]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6303) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.3.
+
+## Overview
+
+[Environments](../environments.md) can be used for different reasons:
+
+- Some of them are just for testing.
+- Others are for production.
+
+Because deploy jobs can be raised by different users with different roles, it is important that
+specific environments are "protected" to avoid unauthorized people affecting them.
+
+By default, a protected environment does one thing: it ensures that only people
+with the right privileges can deploy to it, thus keeping it safe.
+
+NOTE: **Note**:
+A GitLab admin is always allowed to use environments, even if they are protected.
+
+To protect, update, or unprotect an environment, you need to have at least
+[Maintainer permissions](../../user/permissions.md).
+
+## Protecting environments
+
+To protect an environment:
+
+1. Navigate to your project's **Settings > CI/CD**.
+1. Expand the **Protected Environments** section.
+1. From the **Environment** dropdown menu, select the environment you want to protect.
+1. In the **Allowed to Deploy** dropdown menu, select the role, users, or groups you want to have deploy access.
+ There are some considerations to have in mind:
+ - There are two roles to choose from:
+ - **Maintainers**: will allow access to all maintainers in the project.
+ - **Developers**: will allow access to all maintainers and all developers in the project.
+ - You can only select groups that are associated with the project.
+ - Only users that have at least Developer permission level will appear on
+ the **Allowed to Deploy** dropdown menu.
+1. Click the **Protect** button.
+
+The protected environment will now appear in the list of protected environments.
+
+## Modifying and unprotecting environments
+
+Maintainers can:
+
+- Update existing protected environments at any time by changing the access on **Allowed to deploy** dropdown menu.
+- Unprotect a protected environment by clicking the **Unprotect** button of the environment to unprotect.
diff --git a/doc/ci/introduction/img/gitlab_workflow_example.png b/doc/ci/introduction/img/gitlab_workflow_example.png
deleted file mode 100644
index 94e7753c3b2..00000000000
--- a/doc/ci/introduction/img/gitlab_workflow_example.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/introduction/img/gitlab_workflow_example_11_9.png b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png
new file mode 100644
index 00000000000..204e9c462e5
--- /dev/null
+++ b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png
Binary files differ
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
index 1b423a4696e..d505f2ae4ce 100644
--- a/doc/ci/introduction/index.md
+++ b/doc/ci/introduction/index.md
@@ -154,7 +154,7 @@ Once you're happy with your implementation:
- 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.
-<img src="img/gitlab_workflow_example.png" alt="GitLab workflow example" class="image-noshadow">
+<img src="img/gitlab_workflow_example_11_9.png" alt="GitLab workflow example" class="image-noshadow">
GitLab CI/CD is capable of a doing a lot more, but this workflow
exemplifies GitLab's ability to track the entire process,
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index c509c341d1e..38cd58f11ac 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -45,12 +45,12 @@ Pipeline graphs can be displayed in two different ways, depending on the page yo
access the graph from.
NOTE: **Note:**
-GitLab capitalizes the stages' names when shown in the [pipeline graphs](#pipeline-graphs).
+GitLab capitalizes the stages' names when shown in the pipeline graphs (below).
### Regular pipeline graphs
Regular pipeline graphs show the names of the jobs of each stage. Regular pipeline graphs can
-be found when you are on a [single pipeline page](#seeing-pipeline-status). For example:
+be found when you are on a [single pipeline page](#accessing-pipelines). For example:
![Pipelines example](img/pipelines.png)
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 9684cb6ed98..65886400c64 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -105,7 +105,7 @@ Jobs are used to create jobs, which are then picked by
What is important is that each job is run independently from each other.
If you want to check whether the `.gitlab-ci.yml` of your project is valid, there is a
-Lint tool under the page `/ci/lint` of your project namespace. You can also find
+Lint tool under the page `/-/ci/lint` of your project namespace. You can also find
a "CI Lint" button to go to this page under **CI/CD ➔ Pipelines** and
**Pipelines ➔ Jobs** in your project.
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 4d15d58cb40..8827961d86c 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -45,10 +45,11 @@ version of [GitLab Runner](https://docs.gitlab.com/runner/) is used. Consult the
version of Runner required.
NOTE: **Note:**
-Starting with GitLab 9.0, we have deprecated some variables. Read the
-[9.0 Renaming](#gitlab-90-renaming) section to find out their replacements. **You are
-strongly advised to use the new variables as we will remove the old ones in
-future GitLab releases.**
+Starting with GitLab 9.0, we have deprecated some variables.
+Read the [deprecated variables](deprecated_variables.md)
+document to find out their replacements. **You are strongly advised
+to use the new variables as we will remove the old ones in future
+GitLab releases.**
| Variable | GitLab | Runner | Description |
|-------------------------------------------|--------|--------|-------------|
@@ -141,32 +142,6 @@ future GitLab releases.**
| **GITLAB_USER_NAME** | 10.0 | all | The real name of the user who started the job |
| **RESTORE_CACHE_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to restore the cache running a job |
-## GitLab 9.0 renaming
-
-To follow conventions of naming across GitLab, and to further move away from the
-`build` term and toward `job` CI variables have been renamed for the 9.0
-release.
-
-NOTE: **Note:**
-Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are
-strongly advised to use the new variables as we will remove the old ones in
-future GitLab releases.**
-
-| 8.x name | 9.0+ name |
-| --------------------- |------------------------ |
-| `CI_BUILD_ID` | `CI_JOB_ID` |
-| `CI_BUILD_REF` | `CI_COMMIT_SHA` |
-| `CI_BUILD_TAG` | `CI_COMMIT_TAG` |
-| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` |
-| `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` |
-| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` |
-| `CI_BUILD_NAME` | `CI_JOB_NAME` |
-| `CI_BUILD_STAGE` | `CI_JOB_STAGE` |
-| `CI_BUILD_REPO` | `CI_REPOSITORY_URL` |
-| `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` |
-| `CI_BUILD_MANUAL` | `CI_JOB_MANUAL` |
-| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` |
-
## `.gitlab-ci.yml` defined variables
NOTE **Note:**
@@ -311,7 +286,7 @@ variables that were set, etc.
Before enabling this, you should ensure jobs are visible to
[team members only](../../user/permissions.md#project-features). You should
-also [erase](../pipelines.md#seeing-job-status) all generated job traces
+also [erase](../pipelines.md#accessing-individual-jobs) all generated job traces
before making them visible again.
To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`:
diff --git a/doc/ci/variables/deprecated_variables.md b/doc/ci/variables/deprecated_variables.md
new file mode 100644
index 00000000000..2642c9b0eb4
--- /dev/null
+++ b/doc/ci/variables/deprecated_variables.md
@@ -0,0 +1,27 @@
+# Deprecated GitLab CI/CD variables
+
+## GitLab 9.0 renamed variables
+
+To follow conventions of naming across GitLab, and to further move away from the
+`build` term and toward `job`, some [CI/CD environment variables](README.md#predefined-environment-variables) were renamed for GitLab 9.0
+release.
+
+NOTE: **Note:**
+Starting with GitLab 9.0, we have deprecated the `$CI_BUILD_*` variables. **You are
+strongly advised to use the new variables as we will remove the old ones in
+future GitLab releases.**
+
+| 8.x name | 9.0+ name |
+| --------------------- |------------------------ |
+| `CI_BUILD_ID` | `CI_JOB_ID` |
+| `CI_BUILD_REF` | `CI_COMMIT_SHA` |
+| `CI_BUILD_TAG` | `CI_COMMIT_TAG` |
+| `CI_BUILD_BEFORE_SHA` | `CI_COMMIT_BEFORE_SHA` |
+| `CI_BUILD_REF_NAME` | `CI_COMMIT_REF_NAME` |
+| `CI_BUILD_REF_SLUG` | `CI_COMMIT_REF_SLUG` |
+| `CI_BUILD_NAME` | `CI_JOB_NAME` |
+| `CI_BUILD_STAGE` | `CI_JOB_STAGE` |
+| `CI_BUILD_REPO` | `CI_REPOSITORY_URL` |
+| `CI_BUILD_TRIGGERED` | `CI_PIPELINE_TRIGGERED` |
+| `CI_BUILD_MANUAL` | `CI_JOB_MANUAL` |
+| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index f42eb1255de..5690729c370 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -512,9 +512,9 @@ docker build:
- more_scripts/*.{rb,py,sh}
```
-In the scenario above, if you are pushing multiple commits to GitLab to an
-existing branch, GitLab creates and triggers the `docker build` job, provided that
-one of the commits contains changes to either:
+In the scenario above, when pushing multiple commits to GitLab to an existing
+branch, GitLab creates and triggers `docker build` job, provided that one of the
+commits contains changes to either:
- The `Dockerfile` file.
- Any of the files inside `docker/scripts/` directory.
@@ -527,21 +527,20 @@ the section below.
##### Using `changes` with new branches and tags
-If you are pushing a **new** branch or a **new** tag to GitLab, the policy
-always evaluates to true and GitLab will create a job. This feature is not
-connected with merge requests yet, and because GitLab is creating pipelines
-before an user can create a merge request we don't know a target branch at
-this point.
+When pushing a **new** branch or a **new** tag to GitLab, the policy always
+evaluates to true and GitLab will create a job. This feature is not connected
+with merge requests yet and, because GitLab is creating pipelines before a user
+can create a merge request, it is unknown what the target branch is at this point.
##### Using `changes` with `merge_requests`
With [pipelines for merge requests](../merge_request_pipelines/index.md),
-make it possible to define if a job should be created base on files modified
+it is possible to define a job to be created based on files modified
in a merge request.
For example:
-```
+```yaml
docker build service one:
script: docker build -t my-service-one-image:$CI_COMMIT_REF_SLUG .
only:
@@ -552,9 +551,9 @@ docker build service one:
- service-one/**/*
```
-In the scenario above, if you create or update a merge request that changes
-either files in `service-one` folder or `Dockerfile`, GitLab creates and triggers
-the `docker build service one` job.
+In the scenario above, if a merge request is created or updated that changes
+either files in `service-one` directory or the `Dockerfile`, GitLab creates
+and triggers the `docker build service one` job.
### `tags`
diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md
index 9194f847cdf..45f1fed355e 100644
--- a/doc/customization/welcome_message.md
+++ b/doc/customization/welcome_message.md
@@ -1 +1,5 @@
+---
+redirect_to: 'branded_login_page.md'
+---
+
This document was moved to [another location](branded_login_page.md).
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index bfe1ef75914..b39c302453b 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -52,9 +52,12 @@ for audiences of all ages.
If a contributor is no longer actively working on a submitted merge request
we can decide that the merge request will be finished by one of our
[Merge request coaches][team] or close the merge request. We make this decision
-based on how important the change is for our product vision. If a Merge request
+based on how important the change is for our product vision. If a merge request
coach is going to finish the merge request we assign the
-~"coach will finish" label.
+~"coach will finish" label. When a team member picks up a community contribution,
+we credit the original author by adding a changelog entry crediting the author
+and optionally include the original author on at least one of the commits
+within the MR.
## Helping others
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index 0eedef5e14f..45104a1f91d 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -22,6 +22,7 @@
text should be _sorry, we could not create your account because:_
1. Code should be written in [US English][us-english]
1. [Go](../go_guide/index.md)
+1. [Python](../python_guide/index.md)
This is also the style used by linting tools such as
[RuboCop](https://github.com/bbatsov/rubocop) and [Hound CI](https://houndci.com).
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 41a64044c68..c2e05b2d065 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -909,14 +909,15 @@ export default {
- Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](https://docs.gitlab.com/ee/development/ee_features.html#javascript-code-in-assetsjavascripts) alias we already have for webpack.
- This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin
- ##### Example:
- ```javascript
- import mixin from 'ee_else_ce/path/mixin';
+##### Example:
+```javascript
+import mixin from 'ee_else_ce/path/mixin';
- {
+{
mixins: [mixin]
- }
- ```
+}
+```
+
- Computed Properties/methods and getters only used in the child import still need a counterpart in CE
- For store modules, we will need a CE counterpart too.
diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md
index 98e499b8c0f..b23e37d1eef 100644
--- a/doc/development/fe_guide/testing.md
+++ b/doc/development/fe_guide/testing.md
@@ -1 +1,5 @@
-This document was moved to [../testing_guide/frontend_testing.md](../testing_guide/frontend_testing.md).
+---
+redirect_to: '../testing_guide/frontend_testing.md'
+---
+
+This document was moved to [another location](../testing_guide/frontend_testing.md).
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index 1a4b1743a00..27b69ba8278 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -197,3 +197,82 @@ as a [CI environment variable](../ci/variables/README.md#variables).
---
[Return to Development documentation](README.md)
+
+## Wrapping RPCs in Feature Flags
+
+Here are the steps to gate a new feature in Gitaly behind a feature flag.
+
+### Gitaly
+
+1. Create a package scoped flag name:
+
+ ```go
+ var findAllTagsFeatureFlag = "go-find-all-tags"
+ ```
+
+1. Create a switch in the code using the `featureflag` package:
+
+ ```go
+ if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
+ // go implementation
+ } else {
+ // ruby implementation
+ }
+ ```
+
+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"},
+ )
+ )
+
+ func init() {
+ prometheus.Register(findAllTagsRequests)
+ }
+
+ if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
+ findAllTagsRequests.WithLabelValues("go").Inc()
+ // go implementation
+ } else {
+ findAllTagsRequests.WithLabelValues("ruby").Inc()
+ // ruby impelmentation
+ }
+ ```
+
+1. Set headers in tests:
+
+ ```go
+ import (
+ "google.golang.org/grpc/metadata"
+
+ "gitlab.com/gitlab-org/gitaly/internal/featureflag"
+ )
+
+ //...
+
+ md := metadata.New(map[string]string{featureflag.HeaderKey(findAllTagsFeatureFlag): "true"})
+ ctx = metadata.NewOutgoingContext(context.Background(), md)
+
+ c, err = client.FindAllTags(ctx, rpcRequest)
+ require.NoError(t, err)
+ ```
+
+### Gitlab-Rails
+
+1. Add feature flag to `lib/gitlab/gitaly_client.rb` (in gitlab-rails):
+
+ ```ruby
+ SERVER_FEATURE_FLAGS = %w[go-find-all-tags].freeze
+ ```
+
+1. Test in rails console by setting feature flag:
+
+ ```ruby
+ Feature.enable('gitaly_go-find-all-tags')
+ ```
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index 7fd41c5e01f..6dcade3bb51 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -26,7 +26,7 @@ Reviewers and maintainers should pay attention to:
- `defer` functions: ensure the presence when needed, and after `err` check.
- Inject dependencies as parameters.
-- Void structs when marshalling to JSON (generates `null` instead of `[]`).
+- Void structs when marshaling to JSON (generates `null` instead of `[]`).
### Security
@@ -185,7 +185,7 @@ There are a few guidelines one should follow when using the
- When printing an error use
[WithError](https://godoc.org/github.com/sirupsen/logrus#WithError). For
- exmaple, `logrus.WithError(err).Error("Failed to do something")`.
+ example, `logrus.WithError(err).Error("Failed to do something")`.
- Since we use [structured logging](#structured-json-logging) we can log
fields in the context of that code path, such as the URI of the request using
[`WithField`](https://godoc.org/github.com/sirupsen/logrus#WithField) or
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index dd338f6f67d..131e3edf35e 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -126,5 +126,8 @@ are very appreciative of the work done by translators and proofreaders!
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 lanuguage 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.
+
[proofreader-src]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/i18n/proofreader.md
diff --git a/doc/development/i18n_guide.md b/doc/development/i18n_guide.md
index f6e949b5fd8..e588b47e203 100644
--- a/doc/development/i18n_guide.md
+++ b/doc/development/i18n_guide.md
@@ -1 +1,5 @@
-This document was moved to [a new location](i18n/index.md).
+---
+redirect_to: 'i18n/index.md'
+---
+
+This document was moved to [another location](i18n/index.md).
diff --git a/doc/development/python_guide/index.md b/doc/development/python_guide/index.md
new file mode 100644
index 00000000000..6025dc9ebf2
--- /dev/null
+++ b/doc/development/python_guide/index.md
@@ -0,0 +1,79 @@
+# Python Development Guidelines
+
+GitLab requires Python as a dependency for [reStructuredText](http://docutils.sourceforge.net/rst.html)
+markup rendering.
+
+As of GitLab 11.10, we require Python 3.
+
+## Installation
+
+There are several ways of installing python on your system. To be able to use the same version we use in production,
+we suggest you use [pyenv](https://github.com/pyenv/pyenv). It works and behave similar to its counterpart in the
+ruby world: [rbenv](https://github.com/rbenv/rbenv).
+
+### macOS
+
+To install `pyenv` on macOS, you can use [Homebrew](https://brew.sh/) with:
+
+```bash
+brew install pyenv
+```
+
+### Linux
+
+To install `pyenv` on Linux, you can run the command below:
+
+```bash
+curl https://pyenv.run | bash
+```
+
+Alternatively, you may find `pypenv` available as a system package via your distro package manager.
+
+You can read more about it in: <https://github.com/pyenv/pyenv-installer#prerequisites>.
+
+### Shell integration
+
+Pyenv installation will add required changes to Bash. If you use a different shell,
+check for any additional steps required for it.
+
+For Fish, you can install a plugin for [Fisherman](https://github.com/fisherman/fisherman):
+
+```bash
+fisher add fisherman/pyenv
+```
+
+Or for [Oh My Fish](https://github.com/oh-my-fish/oh-my-fish):
+
+```bash
+omf install pyenv
+```
+
+## Dependency management
+
+While GitLab doesn't directly contain any Python scripts, because we depend on Python to render
+[reStructuredText](http://docutils.sourceforge.net/rst.html) markup, we need to keep track on dependencies
+on the main project level, so we can run that on our development machines.
+
+Recently, an equivalent to the `Gemfile` and the [Bundler](https://bundler.io/) project has been introduced to Python:
+`Pipfile` and [Pipenv](https://pipenv.readthedocs.io/en/latest/).
+
+You will now find a `Pipfile` with the dependencies in the root folder. To install them, run:
+
+```bash
+pipenv install
+```
+
+Running this command will install both the required Python version as well as required pip dependencies.
+
+## Use instructions
+
+To run any python code under the Pipenv environment, you need to first start a `virtualenv` based on the dependencies
+of the application. With Pipenv, this is a simple as running:
+
+```bash
+pipenv shell
+```
+
+After running that command, you can run GitLab on the same shell and it will be using the Python and dependencies
+installed from the `pipenv install` command.
+
diff --git a/doc/development/sql.md b/doc/development/sql.md
index 47519d39e74..edeca7fb298 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -155,6 +155,21 @@ The _only_ time you should use `pluck` is when you actually need to operate on
the values in Ruby itself (e.g. write them to a file). In almost all other cases
you should ask yourself "Can I not just use a sub-query?".
+In line with our `CodeReuse/ActiveRecord` cop, you should only use forms like
+`pluck(:id)` or `pluck(:user_id)` within model code. In the former case, you can
+use the `ApplicationRecord`-provided `.pluck_primary_key` helper method instead.
+In the latter, you should add a small helper method to the relevant model.
+
+## Inherit from ApplicationRecord
+
+Most models in the GitLab codebase should inherit from `ApplicationRecord`,
+rather than from `ActiveRecord::Base`. This allows helper methods to be easily
+added.
+
+An exception to this rule exists for models created in database migrations. As
+these should be isolated from application code, they should continue to subclass
+from `ActiveRecord::Base`.
+
## Use UNIONs
UNIONs aren't very commonly used in most Rails applications but they're very
diff --git a/doc/development/testing.md b/doc/development/testing.md
index 45b1519ece8..79ef8e75432 100644
--- a/doc/development/testing.md
+++ b/doc/development/testing.md
@@ -1 +1,5 @@
-This document was moved to [testing_guide/index.md](testing_guide/index.md).
+---
+redirect_to: 'testing_guide/index.md'
+---
+
+This document was moved to [another location](testing_guide/index.md).
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index cfe0e6f70fc..7262c04d746 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -1,5 +1,20 @@
# Testing best practices
+## Test Design
+
+Testing at GitLab is a first class citizen, not an afterthought. It's important we consider the design of our tests
+as we do the design of our features.
+
+When implementing a feature, we think about developing the right capabilities the right way, which helps us
+narrow our scope to a manageable level. When implementing tests for a feature, we must think about developing
+the right tests, but then cover _all_ the important ways the test may fail, which can quickly widen our scope to
+a level that is difficult to manage.
+
+Test heuristics can help solve this problem. They concisely address many of the common ways bugs
+manifest themselves within our code. When designing our tests, take time to review known test heuristics to inform
+our test design. We can find some helpful heuristics documented in the Handbook in the
+[Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section.
+
## Test speed
GitLab has a massive test suite that, without [parallelization], can take hours
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md
index 9837ea515a3..51fe19c3d9e 100644
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ b/doc/development/testing_guide/end_to_end_tests.md
@@ -7,6 +7,24 @@ as expected across the entire software stack and architecture, including
integration of all micro-services and components that are supposed to work
together.
+## Branch naming
+
+If your contribution contains **only** changes under the
+[`qa/` folder](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa), you can
+speed up the CI process by following some branch naming conventions. You have
+three choices:
+
+| Branch name | Valid example |
+|:----------------------|:-----------------------------|
+| Starting with `qa/` | `qa/new-oauth-login-test` |
+| Starting with `qa-` | `qa-new-oauth-login-test` |
+| Ending in `-qa` | `123-new-oauth-login-test-qa` |
+
+If your branch name matches any of the above, it will run only the QA-related
+jobs.
+If it does not, the whole application test suite will run (including QA-related
+jobs).
+
## How do we test GitLab?
We use [Omnibus GitLab][omnibus-gitlab] to build GitLab packages and then we
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index 67e4cfeda0e..ecad9ba48a3 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -33,7 +33,7 @@ changes should be tested.
## [Testing best practices](best_practices.md)
-Everything you should know about how to write good tests: RSpec, FactoryBot,
+Everything you should know about how to write good tests: Test Design, RSpec, FactoryBot,
system tests, parameterized tests etc.
---
diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md
index 5cc014419ad..7bb2e014738 100644
--- a/doc/gitlab-basics/add-merge-request.md
+++ b/doc/gitlab-basics/add-merge-request.md
@@ -8,7 +8,7 @@ request. For more information, check the
---
1. Before you start, you should have already [created a branch](create-branch.md)
- and [pushed your changes](basic-git-commands.md) to GitLab.
+ and [pushed your changes](start-using-git.md#send-changes-to-gitlabcom) to GitLab.
1. Go to the project where you'd like to merge your changes and click on the
**Merge requests** tab.
1. Click on **New merge request** on the right side of the screen.
diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md
index 985a52d88f5..8c0d3561882 100644
--- a/doc/gitlab-basics/create-group.md
+++ b/doc/gitlab-basics/create-group.md
@@ -1,2 +1,5 @@
+---
+redirect_to: '../user/group/index.md#create-a-new-group'
+---
This document was moved to [another location](../user/group/index.md#create-a-new-group).
diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md
index abb163dbf18..818a03a6f02 100644
--- a/doc/gitlab-basics/create-issue.md
+++ b/doc/gitlab-basics/create-issue.md
@@ -1,2 +1,5 @@
+---
+redirect_to: '../user/project/issues/index.md#new-issue'
+---
This document was moved to [another location](../user/project/issues/index.md#new-issue).
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index 3d4259ab7d3..3e99496d531 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -42,7 +42,9 @@ Project templates can pre-populate your project with necessary files to get you
There are two types of project templates:
-- [Built-in templates](#built-in-templates), sourced from the [`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+- [Built-in templates](#built-in-templates), sourced from the following groups:
+ - [`project-templates`](https://gitlab.com/gitlab-org/project-templates)
+ - [`pages`](https://gitlab.com/pages)
- [Custom project templates](#custom-project-templates-premium-only), for custom templates configured by GitLab administrators and users.
### Built-in templates
@@ -50,7 +52,7 @@ There are two types of project templates:
Built-in templates are project templates that are:
- Developed and maintained in the
- [`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+ [`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups.
- Released with GitLab.
To use a built-in template on the **New project** page:
@@ -64,7 +66,7 @@ To use a built-in template on the **New project** page:
TIP: **Tip:**
You can improve the existing built-in templates or contribute new ones on the
-[`project-templates`](https://gitlab.com/gitlab-org/project-templates) group.
+[`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups.
### Custom project templates **[PREMIUM ONLY]**
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index 2fcc9b90157..0000e03f1d7 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -1,11 +1,11 @@
-# Installing GitLab on Amazon Web Services (AWS)
-
-To install GitLab on AWS, you can use the Amazon Machine Images (AMIs) that GitLab
-provides with [each release](https://about.gitlab.com/releases/).
+# Installing GitLab HA on Amazon Web Services (AWS)
This page offers a walkthrough of a common HA (Highly Available) configuration
for GitLab on AWS. You should customize it to accommodate your needs.
+NOTE: **Note**
+For organizations with 300 users or less, the recommended AWS installation method is to launch an EC2 single box [Omnibus Installation](https://about.gitlab.com/install/) and implement a snapshot strategy for backing up the data.
+
## Introduction
GitLab on AWS can leverage many of the services that are already
@@ -55,6 +55,8 @@ 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)
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 4ac81ab3ee7..29cb6ac9164 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -1 +1,5 @@
-This document was moved to [user/markdown.md](../user/markdown.md).
+---
+redirect_to: '../user/markdown.md'
+---
+
+This document was moved to [another location](../user/markdown.md).
diff --git a/doc/operations/README.md b/doc/operations/README.md
index d7a83948b87..319e5f48d38 100644
--- a/doc/operations/README.md
+++ b/doc/operations/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/operations/index.md'
+---
+
This document was moved to [another location](../administration/operations/index.md).
diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md
index 2a1d0a8c8eb..bde7fffd090 100644
--- a/doc/operations/cleaning_up_redis_sessions.md
+++ b/doc/operations/cleaning_up_redis_sessions.md
@@ -1 +1,5 @@
-This document was moved to [administration/operations/cleaning_up_redis_sessions](../administration/operations/cleaning_up_redis_sessions.md).
+---
+redirect_to: '../administration/operations/cleaning_up_redis_sessions.md'
+---
+
+This document was moved to [another location](../administration/operations/cleaning_up_redis_sessions.md).
diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md
index c54bca324a5..57d47e3e9ea 100644
--- a/doc/operations/moving_repositories.md
+++ b/doc/operations/moving_repositories.md
@@ -1 +1,5 @@
-This document was moved to [administration/operations/moving_repositories](../administration/operations/moving_repositories.md).
+---
+redirect_to: '../administration/operations/moving_repositories.md'
+---
+
+This document was moved to [another location](../administration/operations/moving_repositories.md).
diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md
index cf7c3b2e2ed..2df4a6e5648 100644
--- a/doc/operations/sidekiq_memory_killer.md
+++ b/doc/operations/sidekiq_memory_killer.md
@@ -1 +1,5 @@
-This document was moved to [administration/operations/sidekiq_memory_killer](../administration/operations/sidekiq_memory_killer.md).
+---
+redirect_to: '../administration/operations/sidekiq_memory_killer.md'
+---
+
+This document was moved to [another location](../administration/operations/sidekiq_memory_killer.md).
diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md
index fbc9697b755..949f4a66c9d 100644
--- a/doc/operations/unicorn.md
+++ b/doc/operations/unicorn.md
@@ -1 +1,5 @@
-This document was moved to [administration/operations/unicorn](../administration/operations/unicorn.md).
+---
+redirect_to: '../administration/operations/unicorn.md'
+---
+
+This document was moved to [another location](../administration/operations/unicorn.md).
diff --git a/doc/pages/README.md b/doc/pages/README.md
index 7878bce3f10..c67847f1a83 100644
--- a/doc/pages/README.md
+++ b/doc/pages/README.md
@@ -1 +1,5 @@
-This document was moved to [pages/index.md](../user/project/pages/index.md).
+---
+redirect_to: '../user/project/pages/index.md'
+---
+
+This document was moved to [another location](../user/project/pages/index.md).
diff --git a/doc/pages/administration.md b/doc/pages/administration.md
index 4eb3bb32c77..015dd54ec7f 100644
--- a/doc/pages/administration.md
+++ b/doc/pages/administration.md
@@ -1 +1,5 @@
-This document was moved to [administration/pages](../administration/pages/index.md).
+---
+redirect_to: '../administration/pages/index.md'
+---
+
+This document was moved to [another location](../administration/pages/index.md).
diff --git a/doc/pages/getting_started_part_one.md b/doc/pages/getting_started_part_one.md
index 1d63ccb4d2f..a0feed0b477 100644
--- a/doc/pages/getting_started_part_one.md
+++ b/doc/pages/getting_started_part_one.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/pages/getting_started_part_one.md'
+---
+
This document was moved to [another location](../user/project/pages/getting_started_part_one.md).
diff --git a/doc/pages/getting_started_part_three.md b/doc/pages/getting_started_part_three.md
index 1697b5cd6b4..b65247ff7b7 100644
--- a/doc/pages/getting_started_part_three.md
+++ b/doc/pages/getting_started_part_three.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/pages/getting_started_part_three.md'
+---
+
This document was moved to [another location](../user/project/pages/getting_started_part_three.md).
diff --git a/doc/pages/getting_started_part_two.md b/doc/pages/getting_started_part_two.md
index a58affec73d..05353c171fc 100644
--- a/doc/pages/getting_started_part_two.md
+++ b/doc/pages/getting_started_part_two.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/pages/getting_started_part_two.md'
+---
+
This document was moved to [another location](../user/project/pages/getting_started_part_two.md).
diff --git a/doc/raketasks/check.md b/doc/raketasks/check.md
index f7f6a40cd04..ceb089e80c0 100644
--- a/doc/raketasks/check.md
+++ b/doc/raketasks/check.md
@@ -1,3 +1,5 @@
-# Check Rake Tasks
+---
+redirect_to: '../administration/raketasks/check.md'
+---
-This document was moved to [administration/raketasks/check](../administration/raketasks/check.md).
+This document was moved to [another location](../administration/raketasks/check.md).
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 266aeb7d60e..f554a09d94d 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -1,3 +1,5 @@
-# Maintenance Rake Tasks
+---
+redirect_to: '../administration/raketasks/maintenance.md'
+---
-This document was moved to [administration/raketasks/maintenance](../administration/raketasks/maintenance.md).
+This document was moved to [another location](../administration/raketasks/maintenance.md).
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index fb3f9711711..fd99b06b863 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -444,13 +444,6 @@ This is an optional step, since many projects do not have a Kubernetes cluster
available. If the [requirements](#requirements) are not met, the job will
silently be skipped.
-CAUTION: **Caution:**
-Your apps should *not* be manipulated outside of Helm (using Kubernetes directly.)
-This can cause confusion with Helm not detecting the change, and subsequent
-deploys with Auto DevOps can undo your changes. Also, if you change something
-and want to undo it by deploying again, Helm may not detect that anything changed
-in the first place, and thus not realize that it needs to re-apply the old config.
-
[Review Apps][review-app] are temporary application environments based on the
branch's code so developers, designers, QA, product managers, and other
reviewers can actually see and interact with code changes as part of the review
@@ -466,6 +459,24 @@ up in the merge request widget for easy discovery. When the branch or tag is del
for example after the merge request is merged, the Review App will automatically
be deleted.
+Review apps are deployed using the
+[auto-deploy-app](https://gitlab.com/charts/auto-deploy-app) chart with
+Helm. The app will be deployed into the [Kubernetes
+namespace](../../user/project/clusters/index.md#deployment-variables)
+for the environment.
+
+Since GitLab 11.4, a [local
+Tiller](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22036) is
+used. Previous versions of GitLab had a Tiller installed in the project
+namespace.
+
+CAUTION: **Caution:**
+Your apps should *not* be manipulated outside of Helm (using Kubernetes directly).
+This can cause confusion with Helm not detecting the change and subsequent
+deploys with Auto DevOps can undo your changes. Also, if you change something
+and want to undo it by deploying again, Helm may not detect that anything changed
+in the first place, and thus not realize that it needs to re-apply the old config.
+
### Auto DAST **[ULTIMATE]**
> Introduced in [GitLab Ultimate][ee] 10.4.
@@ -504,13 +515,6 @@ This is an optional step, since many projects do not have a Kubernetes cluster
available. If the [requirements](#requirements) are not met, the job will
silently be skipped.
-CAUTION: **Caution:**
-Your apps should *not* be manipulated outside of Helm (using Kubernetes directly.)
-This can cause confusion with Helm not detecting the change, and subsequent
-deploys with Auto DevOps can undo your changes. Also, if you change something
-and want to undo it by deploying again, Helm may not detect that anything changed
-in the first place, and thus not realize that it needs to re-apply the old config.
-
After a branch or merge request is merged into the project's default branch (usually
`master`), Auto Deploy deploys the application to a `production` environment in
the Kubernetes cluster, with a namespace based on the project name and unique
@@ -523,6 +527,24 @@ enable them.
You can make use of [environment variables](#environment-variables) to automatically
scale your pod replicas.
+Apps are deployed using the
+[auto-deploy-app](https://gitlab.com/charts/auto-deploy-app) chart with
+Helm. The app will be deployed into the [Kubernetes
+namespace](../../user/project/clusters/index.md#deployment-variables)
+for the environment.
+
+Since GitLab 11.4, a [local
+Tiller](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22036) is
+used. Previous versions of GitLab had a Tiller installed in the project
+namespace.
+
+CAUTION: **Caution:**
+Your apps should *not* be manipulated outside of Helm (using Kubernetes directly).
+This can cause confusion with Helm not detecting the change and subsequent
+deploys with Auto DevOps can undo your changes. Also, if you change something
+and want to undo it by deploying again, Helm may not detect that anything changed
+in the first place, and thus not realize that it needs to re-apply the old config.
+
> [Introduced][ce-19507] in GitLab 11.0.
For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
@@ -652,7 +674,7 @@ repo or by specifying a project variable:
### Custom Helm chart per environment **[PREMIUM]**
-You can specify the use of a custom Helm chart per environment by scoping the environment variable
+You can specify the use of a custom Helm chart per environment by scoping the environment variable
to the desired environment. See [Limiting environment scopes of variables](https://docs.gitlab.com/ee/ci/variables/#limiting-environment-scopes-of-variables-premium).
### Customizing `.gitlab-ci.yml`
@@ -901,7 +923,7 @@ increasing the rollout up to 100%.
If `INCREMENTAL_ROLLOUT_MODE` is set to `manual` in your project, then instead
of the standard `production` job, 4 different
-[manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph)
+[manual jobs](../../ci/pipelines.md#manual-actions-from-pipeline-graphs)
will be created:
1. `rollout 10%`
@@ -1022,10 +1044,9 @@ planned for a subsequent release.
buildpack](#custom-buildpacks).
- Auto Test may fail because of a mismatch between testing frameworks. In this
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
-- Auto Deploy may fail if it is unable to create a Kubernetes namespace and
- service account for your project. See the
- [troubleshooting failed deployments](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs)
- section to debug why these resources were not created.
+- Auto Deploy will fail if GitLab can not create a Kubernetes namespace and
+ service account for your project. For help debugging this issue, see
+ [Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs).
### Disable the banner instance wide
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 5dc798fd8d3..2fb6cec55fa 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -71,6 +71,14 @@ or over the size limit, you can [reduce your repository size with Git](../projec
| ----------- | ----------------- | ------------- |
| Repository size including LFS | 10G | Unlimited |
+## IP range
+
+GitLab.com, CI/CD, and related services are deployed into Google Cloud Platform (GCP). Any
+IP based firewall can be configured by looking up all
+[IP address ranges or CIDR blocks for GCP](https://cloud.google.com/compute/docs/faq#where_can_i_find_product_name_short_ip_ranges).
+
+[Static endpoints](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5071) are being considered.
+
## Shared Runners
Shared Runners on GitLab.com run in [autoscale mode] and powered by
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index e67795b9bae..984881ef26c 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -28,6 +28,7 @@ deployments.
| [Helm Tiller](https://docs.helm.sh) | 11.6+ | 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. | n/a |
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress) | 11.6+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
| [Cert-Manager](https://docs.cert-manager.io/en/latest/) | 11.6+ | Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
+| [GitLab Runner](https://docs.gitlab.com/runner/) | 11.10+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](../../../ci/README.md), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](../../project/clusters/index.md#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
NOTE: **Note:**
Some [cluster
@@ -35,8 +36,6 @@ applications](../../project/clusters/index.md#installing-applications)
are installable only for a project-level cluster. Support for installing these
applications in a group-level cluster is planned for future releases. For updates, see:
-- Support installing [Runner in group-level
- clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/51988)
- Support installing [JupyterHub in group-level
clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/51989)
- Support installing [Prometheus in group-level
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index d8bc3a9187e..8239742969a 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -267,10 +267,7 @@ However the wrapping tags cannot be mixed as such:
### Emoji
-> If this is not rendered correctly, see
-https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji
-
-```
+```md
Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you:
:zap: You can use emoji anywhere GFM is supported. :v:
@@ -288,15 +285,15 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he
Ubuntu 18.04 (like many modern Linux distros) has this font installed by default.
```
-Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/monkey.png" width="20px" height="20px"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/star2.png" width="20px" height="20px"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speech_balloon.png" width="20px" height="20px">. Well we have a gift for you:
+Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/monkey.png" width="20px" height="20px" style="display:inline;margin:0"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/star2.png" width="20px" height="20px" style="display:inline;margin:0"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0">. Well we have a gift for you:
-<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/zap.png" width="20px" height="20px">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/v.png" width="20px" height="20px">
+<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/zap.png" width="20px" height="20px" style="display:inline;margin:0">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/v.png" width="20px" height="20px" style="display:inline;margin:0">
-You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/bug.png" width="20px" height="20px"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speak_no_evil.png" width="20px" height="20px"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/snail.png" width="20px" height="20px"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/birthday.png" width="20px" height="20px">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/heart.png" width="20px" height="20px"> you for that.
+You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/bug.png" width="20px" height="20px" style="display:inline;margin:0"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/snail.png" width="20px" height="20px" style="display:inline;margin:0"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/birthday.png" width="20px" height="20px" style="display:inline;margin:0">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/heart.png" width="20px" height="20px" style="display:inline;margin:0"> you for that.
-If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/fearful.png" width="20px" height="20px">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/family.png" width="20px" height="20px">. All you need to do is to look up one of the supported codes.
+If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes.
-Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/thumbsup.png" width="20px" height="20px">
+Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0">
Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index adc0f4d568b..a340dd063e4 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -119,7 +119,7 @@ The following table depicts the various user permission levels in a project.
| Force push to protected branches [^4] | | | | | |
| Remove protected branches [^4] | | | | | |
| View project Audit Events | | | | ✓ | ✓ |
-| View project statistics | | | | ✓ | ✓ |
+| View project statistics | | ✓ | ✓ | ✓ | ✓ |
## Project features permissions
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index b74bd81d467..8b3ae19b544 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -18,9 +18,10 @@ the second factor of authentication. Once enabled, in addition to supplying your
password to login, you'll be prompted to activate your U2F device (usually by pressing
a button on it), and it will perform secure authentication on your behalf.
-The U2F workflow is only [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera and Firefox at this point, so we _strongly_ recommend
-that you set up both methods of two-factor authentication, so you can still access your account
-from other browsers.
+The U2F workflow is [supported by](https://caniuse.com/#search=U2F) Google Chrome, Opera, and Firefox.
+
+We recommend that you set up 2FA with both a [one-time password authenticator](#enable-2fa-via-one-time-password-authenticator) and a [U2F device](#enable-2fa-via-u2f-device), so you can still access your account
+if you lose your U2F device.
## Enabling 2FA
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 3a4d09c35d9..52b4a72e688 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -5,12 +5,9 @@
Personal access tokens are the preferred way for third party applications and scripts to
authenticate with the [GitLab API][api], if using [OAuth2](../../api/oauth2.md) is not practical.
-You can also use them to authenticate against Git over HTTP. They are the only
-accepted method of authentication when you have
-[Two-Factor Authentication (2FA)][2fa] enabled.
+You can also use personal access tokens to authenticate against Git over HTTP or SSH. They must be used when you have [Two-Factor Authentication (2FA)][2fa] enabled. Authenticate with a token in place of your password.
-Once you have your token, [pass it to the API][usage] using either the
-`private_token` parameter or the `Private-Token` header.
+To make [authenticated requests to the API][usage], use either the `private_token` parameter or the `Private-Token` header.
The expiration of personal access tokens happens on the date you define,
at midnight UTC.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 5a74ac96e83..3a9a3b4a423 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -101,14 +101,20 @@ To add an existing Kubernetes cluster to your project:
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
+ - 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:
+ - Get the certificate by running this command:
- ```sh
- kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode
- ```
+ ```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`.
@@ -124,23 +130,7 @@ To add an existing Kubernetes cluster to your project:
metadata:
name: gitlab-admin
namespace: kube-system
- ```
-
- 2. Apply the service account to your cluster:
-
- ```bash
- kubectl apply -f gitlab-admin-service-account.yaml
- ```
-
- Output:
-
- ```bash
- serviceaccount "gitlab-admin" created
- ```
-
- 3. Create a file called `gitlab-admin-cluster-role-binding.yaml` with contents:
-
- ```yaml
+ ---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
@@ -155,41 +145,42 @@ To add an existing Kubernetes cluster to your project:
namespace: kube-system
```
- 4. Apply the cluster role binding to your cluster:
+ 1. Apply the service account and cluster role binding to your cluster:
```bash
- kubectl apply -f gitlab-admin-cluster-role-binding.yaml
+ kubectl apply -f gitlab-admin-service-account.yaml
```
Output:
```bash
+ serviceaccount "gitlab-admin" created
clusterrolebinding "gitlab-admin" created
```
- 5. Retrieve the token for the `gitlab-admin` service account:
+ 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:
+ 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
+ ```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
+ Type: kubernetes.io/service-account-token
- Data
- ====
- ca.crt: 1025 bytes
- namespace: 11 bytes
- token: <authentication_token>
- ```
+ Data
+ ====
+ ca.crt: 1025 bytes
+ namespace: 11 bytes
+ token: <authentication_token>
+ ```
NOTE: **Note:**
For GKE clusters, you will need the
@@ -212,14 +203,6 @@ To add an existing Kubernetes cluster to your project:
After a couple of minutes, your cluster will be ready to go. You can now proceed
to install some [pre-defined applications](#installing-applications).
-To determine the:
-
-- API URL, run `kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'`.
-- Token:
- 1. List the secrets by running: `kubectl get secrets`. Note the name of the secret you need the token for.
- 1. Get the token for the appropriate secret by running: `kubectl get secret <SECRET_NAME> -o jsonpath="{['data']['token']}" | base64 --decode`.
-- CA certificate, run `kubectl get secret <secret name> -o jsonpath="{['data']['ca\.crt']}" | base64 --decode`.
-
## Security implications
CAUTION: **Important:**
@@ -360,8 +343,8 @@ by GitLab before installing any of the applications.
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
| [Cert-Manager](https://docs.cert-manager.io/en/latest/) | 11.6+ | Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
-| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
-| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. Authentication will be enabled only for [project members](../members/index.md) with [Developer or higher](../../permissions.md) access to the project. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
+| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](../../../ci/README.md), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
+| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. Authentication will be enabled only for [project members](../members/index.md) with [Developer or higher](../../permissions.md) access to the project. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). Note that Ingress must be installed and have an IP address assigned before JupyterHub can be installed. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
| [Knative](https://cloud.google.com/knative) | 11.5+ | Knative provides a platform to create, deploy, and manage serverless workloads from a Kubernetes cluster. It is used in conjunction with, and includes [Istio](https://istio.io) to provide an external IP address for all programs hosted by Knative. You will be prompted to enter a wildcard domain where your applications will be exposed. Configure your DNS server to use the external IP address for that domain. For any application created and installed, they will be accessible as `<program_name>.<kubernetes_namespace>.<domain_name>`. This will require your kubernetes cluster to have [RBAC enabled](#role-based-access-control-rbac). | [knative/knative](https://storage.googleapis.com/triggermesh-charts)
With the exception of Knative, the applications will be installed in a dedicated
@@ -406,14 +389,20 @@ to obtain the endpoint. You can use either
In order to publish your web application, you first need to find the endpoint which will be either an IP
address or a hostname associated with your load balancer.
-### Let GitLab fetch the external endpoint
+### Automatically determining the external endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
-If you [installed Ingress or Knative](#installing-applications),
-you should see the Ingress Endpoint on this same page within a few minutes.
-If you don't see this, GitLab might not be able to determine the external endpoint of
-your ingress application in which case you should manually determine it.
+After you install [Ingress or Knative](#installing-applications), Gitlab attempts to determine the external endpoint
+and it should be available within a few minutes. If the endpoint doesn't appear
+and your cluster runs on Google Kubernetes Engine:
+
+1. Check your [Kubernetes cluster on Google Kubernetes Engine](https://console.cloud.google.com/kubernetes) to ensure there are no errors on its nodes.
+1. Ensure you have enough [Quotas](https://console.cloud.google.com/iam-admin/quotas) on Google Kubernetes Engine. For more information, see [Resource Quotas](https://cloud.google.com/compute/quotas).
+1. Check [Google Cloud's Status](https://status.cloud.google.com/) to ensure they are not having any disruptions.
+
+If GitLab is still unable to determine the endpoint of your Ingress or Knative application, you can
+manually determine it by following the steps below.
### Manually determining the external endpoint
@@ -559,25 +548,23 @@ service account of the cluster integration.
### Troubleshooting failed deployment jobs
GitLab will create a namespace and service account specifically for your
-deployment jobs. These resources are created just before the deployment
-job starts. Sometimes there may be errors that cause their creation to fail.
+deployment jobs, immediately before the jobs starts.
-In such instances, your job will fail with the message:
+However, sometimes GitLab can not create them. In such instances, your job will fail with the message:
-```The job failed to complete prerequisite tasks```
+```text
+The job failed to complete prerequisite tasks
+```
-You will need to check the [logs](../../../administration/logs.md) to debug
-why the namespace and service account creation failed.
+To find the cause of this error when creating a namespace and service account, check the [logs](../../../administration/logs.md#sidekiqlog).
-A common reason for failure is that the token you gave GitLab did not have
-[`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
-privileges as GitLab expects.
+Common reasons for failure include:
-Another common problem is caused by a missing `KUBECONFIG` or `KUBE_TOKEN`.
-To be passed to your job, it must have a matching
-[`environment:name`](../../../ci/environments.md#defining-environments). If
-your job has no `environment:name` set, it will not be passed the Kubernetes
-credentials.
+- The token you gave GitLab did not have [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
+ privileges required by GitLab.
+- Missing `KUBECONFIG` or `KUBE_TOKEN` variables. To be passed to your job, they must have a matching
+ [`environment:name`](../../../ci/environments.md#defining-environments). If your job has no
+ `environment:name` set, it will not be passed the Kubernetes credentials.
## Monitoring your Kubernetes cluster **[ULTIMATE]**
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 4d19464cb7a..64139f9dbe9 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -55,11 +55,11 @@ When you create a project in GitLab, you'll have access to a large number of
- [Auto Deploy](../../ci/autodeploy/index.md): Configure GitLab CI/CD
to automatically set up your app's deployment
- [Enable and disable GitLab CI](../../ci/enable_or_disable_ci.md)
- - [Pipelines](../../ci/pipelines.md#pipelines): Configure and visualize
+ - [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#pipeline-graphs): View your
+ - [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
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index d324e0de0cc..8e1603f9ec9 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -48,7 +48,7 @@ Navigate to the webhooks page by going to your project's
## Use-cases
- You can set up a webhook in GitLab to send a notification to
- [Slack](https://api.slack.com/incoming-webhooks) every time a build fails, for example
+ [Slack](https://api.slack.com/incoming-webhooks) every time a job fails.
- You can [integrate with Twilio to be notified via SMS](https://www.datadoghq.com/blog/send-alerts-sms-customizable-webhooks-twilio/)
every time an issue is created for a specific project or group within GitLab
- You can use them to [automatically assign labels to merge requests](https://about.gitlab.com/2016/08/19/applying-gitlab-labels-automatically/).
@@ -1004,7 +1004,7 @@ X-Gitlab-Event: Pipeline Hook
"email": "user@gitlab.com"
}
},
- "builds":[
+ "jobs":[
{
"id": 380,
"stage": "deploy",
@@ -1129,34 +1129,34 @@ X-Gitlab-Event: Pipeline Hook
}
```
-### Build events
+### Job events
-Triggered on status change of a Build.
+Triggered on status change of a job.
**Request Header**:
```
-X-Gitlab-Event: Build Hook
+X-Gitlab-Event: Job Hook
```
**Request Body**:
```json
{
- "object_kind": "build",
+ "object_kind": "job",
"ref": "gitlab-script-trigger",
"tag": false,
"before_sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
"sha": "2293ada6b400935a1378653304eaf6221e0fdb8f",
- "build_id": 1977,
- "build_name": "test",
- "build_stage": "test",
- "build_status": "created",
- "build_started_at": null,
- "build_finished_at": null,
- "build_duration": null,
- "build_allow_failure": false,
- "build_failure_reason": "script_failure",
+ "job_id": 1977,
+ "job_name": "test",
+ "job_stage": "test",
+ "job_status": "created",
+ "job_started_at": null,
+ "job_finished_at": null,
+ "job_duration": null,
+ "job_allow_failure": false,
+ "job_failure_reason": "script_failure",
"project_id": 380,
"project_name": "gitlab-org/gitlab-test",
"user": {
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 593eb80e044..01a3a5bbbe1 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -18,7 +18,7 @@ With GitLab merge requests, you can:
- 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)
-- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#pipeline-graphs)
+- View the deployment process through [Pipeline Graphs](../../../ci/pipelines.md#visualizing-pipelines)
- [Automatically close the issue(s)](../../project/issues/closing_issues.md#via-merge-request) that originated the implementation proposed in the merge request
- Assign it to any registered user, and change the assignee how many times you need
- Assign a [milestone](../../project/milestones/index.md) and track the development of a broader implementation
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 2911a56cf67..89f1beb6d1f 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -2,29 +2,33 @@
> **Notes**:
>
-> - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533].
-> - In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853].
+> - Introduced in GitLab 9.1 as [Trigger Schedule](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533).
+> - [Renamed to Pipeline Schedule](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853) in GitLab 9.2.
> - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
-Pipeline schedules can be used to run a pipeline at specific intervals, for example every
-month on the 22nd for a certain branch.
+Pipelines are normally run based on certain conditions being met. For example, when a branch is pushed to repository.
-## Using Pipeline schedules
+Pipeline schedules can be used to also run [pipelines](../../../ci/pipelines.md) at specific intervals. For example:
-In order to schedule a pipeline:
+- Every month on the 22nd for a certain branch.
+- Once every day.
-1. Navigate to your project's **CI / CD ➔ Schedules** and click the
- **New Schedule** button.
-1. Fill in the form
-1. Hit **Save pipeline schedule** for the changes to take effect.
+In addition to using the GitLab UI, pipeline schedules can be maintained using the
+[Pipeline schedules API](../../../api/pipeline_schedules.md).
+
+## Configuring pipeline schedules
+
+To schedule a pipeline for project:
+
+1. Navigate to the project's **CI / CD > Schedules** page.
+1. Click the **New schedule** button.
+1. Fill in the **Schedule a new pipeline** form.
+1. Click the **Save pipeline schedule** button.
![New Schedule Form](img/pipeline_schedules_new_form.png)
-> **Attention:**
-The pipelines won't be executed precisely, because schedules are handled by
-Sidekiq, which runs according to its interval.
-See [advanced admin configuration](#advanced-admin-configuration) for more
-information.
+NOTE: **Note:**
+Pipelines execution [timing is dependent](#advanced-configuration) on Sidekiq's own schedule.
In the **Schedules** index page you can see a list of the pipelines that are
scheduled to run. The next run is automatically calculated by the server GitLab
@@ -32,36 +36,24 @@ is installed on.
![Schedules list](img/pipeline_schedules_list.png)
-### Running a scheduled pipeline manually
-
-> [Introduced][ce-15700] in GitLab 10.4.
-
-To trigger a pipeline schedule manually, click the "Play" button:
-
-![Play Pipeline Schedule](img/pipeline_schedule_play.png)
-
-This will schedule a background job to run the pipeline schedule. A flash
-message will provide a link to the CI/CD Pipeline index page.
-
-To help avoid abuse, users are rate limited to triggering a pipeline once per
-minute.
-
-### Making use of scheduled pipeline variables
+### Using variables
-> [Introduced][ce-12328] in GitLab 9.4.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328) in GitLab 9.4.
You can pass any number of arbitrary variables and they will be available in
-GitLab CI so that they can be used in your `.gitlab-ci.yml` file.
+GitLab CI so that they can be used in your [`.gitlab-ci.yml` file](../../../ci/yaml/README.md).
![Scheduled pipeline variables](img/pipeline_schedule_variables.png)
-## Using only and except
+### Using only and except
To configure that a job can be executed only when the pipeline has been
scheduled (or the opposite), you can use
[only and except](../../../ci/yaml/README.md#onlyexcept-basic) configuration keywords.
-```
+For example:
+
+```yaml
job:on-schedule:
only:
- schedules
@@ -75,11 +67,47 @@ job:
- make build
```
-## Taking ownership
+### Advanced configuration
+
+The pipelines won't be executed exactly on schedule because schedules are handled by
+Sidekiq, which runs according to its interval.
+
+For example, only two pipelines will be created per day if:
-Pipelines are executed as a user, who owns a schedule. This influences what
-projects and other resources the pipeline has access to. If a user does not own
-a pipeline, you can take ownership by clicking the **Take ownership** button.
+- You set a schedule to create a pipeline every minute (`* * * * *`).
+- The Sidekiq worker runs on 00:00 and 12:00 every day (`0 */12 * * *`).
+
+To change the Sidekiq worker's frequency:
+
+1. Edit the `pipeline_schedule_worker_cron` value in your instance's `gitlab.rb` file.
+1. [Restart GitLab](../../../administration/restart_gitlab.md).
+
+For GitLab.com, refer to the [dedicated settings page](../../gitlab_com/index.md#cron-jobs).
+
+## Working with scheduled pipelines
+
+Once configured, GitLab supports many functions for working with scheduled pipelines.
+
+### Running manually
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15700) in GitLab 10.4.
+
+To trigger a pipeline schedule manually, click the "Play" button:
+
+![Play Pipeline Schedule](img/pipeline_schedule_play.png)
+
+This will schedule a background job to run the pipeline schedule. A flash
+message will provide a link to the CI/CD Pipeline index page.
+
+NOTE: **Note:**
+To help avoid abuse, users are rate limited to triggering a pipeline once per
+minute.
+
+### Taking ownership
+
+Pipelines are executed as a user, who owns a schedule. This influences what projects and other resources the pipeline has access to.
+
+If a user does not own a pipeline, you can take ownership by clicking the **Take ownership** button.
The next time a pipeline is scheduled, your credentials will be used.
![Schedules list](img/pipeline_schedules_ownership.png)
@@ -90,20 +118,3 @@ on the target branch, the schedule will stop creating new pipelines. This can
happen if, for example, the owner is blocked or removed from the project, or
the target branch or tag is protected. In this case, someone with sufficient
privileges must take ownership of the schedule.
-
-## Advanced admin configuration
-
-The pipelines won't be executed precisely, because schedules are handled by
-Sidekiq, which runs according to its interval. For example, if you set a
-schedule to create a pipeline every minute (`* * * * *`) and the Sidekiq worker
-runs on 00:00 and 12:00 every day (`0 */12 * * *`), only 2 pipelines will be
-created per day. To change the Sidekiq worker's frequency, you have to edit the
-`pipeline_schedule_worker_cron` value in your `gitlab.rb` and restart GitLab.
-For GitLab.com, you can check the [dedicated settings page][settings]. If you
-don't have admin access to the server, ask your administrator.
-
-[ce-10533]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533
-[ce-10853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853
-[ce-12328]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12328
-[ce-15700]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15700
-[settings]: https://about.gitlab.com/gitlab-com/settings/#cron-jobs
diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md
index 532247a98cd..95f606fd786 100644
--- a/doc/user/project/wiki/index.md
+++ b/doc/user/project/wiki/index.md
@@ -25,7 +25,7 @@ message.
## Creating a new wiki page
NOTE: **Note:**
-A [permission level][permissions] of **Developer** is needed to create Wiki pages.
+Requires Developer [permissions](../../permissions.md).
Create a new page by clicking the **New page** button that can be found
in all wiki pages. You will be asked to fill in the page name from which GitLab
@@ -58,7 +58,7 @@ repository, you will have to upload them again.
## Editing a wiki page
NOTE: **Note:**
-A [permission level][permissions] of **Developer** is needed to edit Wiki pages.
+Requires Developer [permissions](../../permissions.md).
To edit a page, simply click on the **Edit** button. From there on, you can
change its content. When done, click **Save changes** for the changes to take
@@ -67,7 +67,7 @@ effect.
## Deleting a wiki page
NOTE: **Note:**
-A [permission level][permissions] of **Maintainer** is needed to delete Wiki pages.
+Requires Maintainer [permissions](../../permissions.md).
You can find the **Delete** button only when editing a page. Click on it and
confirm you want the page to be deleted.
@@ -114,8 +114,6 @@ them like you would do with every other Git repository.
On the right sidebar, click on **Clone repository** and follow the on-screen
instructions.
-[permissions]: ../../permissions.md
-
## Customizing sidebar
By default, the wiki would render a sidebar which lists all the pages for the
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index 35cc080d2b7..f1ec771dd9a 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/members/index.md'
+---
+
This document was moved to [../../user/project/members/index.md](../../user/project/members/index.md)
diff --git a/doc/workflow/authorization_for_merge_requests.md b/doc/workflow/authorization_for_merge_requests.md
index 7bf80a3ad0d..8e43d340613 100644
--- a/doc/workflow/authorization_for_merge_requests.md
+++ b/doc/workflow/authorization_for_merge_requests.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/authorization_for_merge_requests.md'
+---
+
This document was moved to [user/project/merge_requests/authorization_for_merge_requests](../user/project/merge_requests/authorization_for_merge_requests.md)
diff --git a/doc/workflow/award_emoji.md b/doc/workflow/award_emoji.md
index d74378cc564..02db97b8dd6 100644
--- a/doc/workflow/award_emoji.md
+++ b/doc/workflow/award_emoji.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/award_emojis.md'
+---
+
This document was moved to [another location](../user/award_emojis.md).
diff --git a/doc/workflow/cherry_pick_changes.md b/doc/workflow/cherry_pick_changes.md
index 663ffd3f746..29c4f854416 100644
--- a/doc/workflow/cherry_pick_changes.md
+++ b/doc/workflow/cherry_pick_changes.md
@@ -1 +1,5 @@
-This document was moved to [user/project/merge_requests/cherry_pick_changes](../user/project/merge_requests/cherry_pick_changes.md).
+---
+redirect_to: '../user/project/merge_requests/cherry_pick_changes.md'
+---
+
+This document was moved to [another location](../user/project/merge_requests/cherry_pick_changes.md).
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
index e0a445920b4..29da321ba46 100644
--- a/doc/workflow/importing/README.md
+++ b/doc/workflow/importing/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/index.md'
+---
+
This document was moved to [another location](../../user/project/import/index.md).
diff --git a/doc/workflow/importing/import_projects_from_bitbucket.md b/doc/workflow/importing/import_projects_from_bitbucket.md
index ec9a11f390e..a42ba7d4518 100644
--- a/doc/workflow/importing/import_projects_from_bitbucket.md
+++ b/doc/workflow/importing/import_projects_from_bitbucket.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/bitbucket.md'
+---
+
This document was moved to [another location](../../user/project/import/bitbucket.md).
diff --git a/doc/workflow/importing/import_projects_from_fogbugz.md b/doc/workflow/importing/import_projects_from_fogbugz.md
index 876eb0434f0..f5c791dc6de 100644
--- a/doc/workflow/importing/import_projects_from_fogbugz.md
+++ b/doc/workflow/importing/import_projects_from_fogbugz.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/fogbugz.md'
+---
+
This document was moved to [another location](../../user/project/import/fogbugz.md).
diff --git a/doc/workflow/importing/import_projects_from_gitea.md b/doc/workflow/importing/import_projects_from_gitea.md
index 8b55b6c23eb..df053835b44 100644
--- a/doc/workflow/importing/import_projects_from_gitea.md
+++ b/doc/workflow/importing/import_projects_from_gitea.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/gitea.md'
+---
+
This document was moved to [another location](../../user/project/import/gitea.md).
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index 72dfe5403c3..6397fcc74b8 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/github.md'
+---
+
This document was moved to [another location](../../user/project/import/github.md).
diff --git a/doc/workflow/importing/import_projects_from_gitlab_com.md b/doc/workflow/importing/import_projects_from_gitlab_com.md
index 3256088c014..135b9704df9 100644
--- a/doc/workflow/importing/import_projects_from_gitlab_com.md
+++ b/doc/workflow/importing/import_projects_from_gitlab_com.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/gitlab_com.md'
+---
+
This document was moved to [another location](../../user/project/import/gitlab_com.md).
diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md
index 32a75a6c6af..99f13d6354c 100644
--- a/doc/workflow/importing/migrating_from_svn.md
+++ b/doc/workflow/importing/migrating_from_svn.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/import/svn.md'
+---
+
This document was moved to [another location](../../user/project/import/svn.md).
diff --git a/doc/workflow/labels.md b/doc/workflow/labels.md
index 5c09891dfdd..3d07d411dd4 100644
--- a/doc/workflow/labels.md
+++ b/doc/workflow/labels.md
@@ -1,3 +1,7 @@
+---
+redirect_to: '../user/project/labels.md'
+---
+
# Labels
This document was moved to [user/project/labels.md](../user/project/labels.md).
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 3fb553280d0..6d941135bf2 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -108,6 +108,18 @@ Here is a configuration example with GCS.
_NOTE: The service account must have permission to access the bucket. [See more](https://cloud.google.com/storage/docs/authentication)_
+Here is a configuration example with Rackspace Cloud Files.
+
+| Setting | Description | example |
+|---------|-------------|---------|
+| `provider` | The provider name | `Rackspace` |
+| `rackspace_username` | The username of the Rackspace account with access to the container | `joe.smith` |
+| `rackspace_api_key` | The API key of the Rackspace account with access to the container | `ABC123DEF456ABC123DEF456ABC123DE` |
+| `rackspace_region` | The Rackspace storage region to use, a three letter code from the [list of service access endpoints](https://developer.rackspace.com/docs/cloud-files/v1/general-api-info/service-access/) | `iad` |
+| `rackspace_temp_url_key` | The private key you have set in the Rackspace API for temporary URLs. Read more [here](https://developer.rackspace.com/docs/cloud-files/v1/use-cases/public-access-to-your-cloud-files-account/#tempurl) | `ABC123DEF456ABC123DEF456ABC123DE` |
+
+_NOTES: Regardless of whether the container has public access enabled or disabled, Fog will use the TempURL method to grant access to LFS objects. If you see errors in logs referencing instantiating storage with a temp-url-key, ensure that you have set they key properly on the Rackspace API and in gitlab.rb. You can verify the value of the key Rackspace has set by sending a GET request with token header to the service access endpoint URL and comparing the output of the returned headers._
+
### Manual uploading to an object storage
There are two ways to manually do the same thing as automatic uploading (described above).
diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md
index dc6da1938f3..fd9f9b81bc9 100644
--- a/doc/workflow/merge_requests.md
+++ b/doc/workflow/merge_requests.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/index.md'
+---
+
This document was moved to [user/project/merge_requests/index.md](../user/project/merge_requests/index.md).
diff --git a/doc/workflow/merge_when_build_succeeds.md b/doc/workflow/merge_when_build_succeeds.md
index b4f6d6117de..41e6ff0cdd6 100644
--- a/doc/workflow/merge_when_build_succeeds.md
+++ b/doc/workflow/merge_when_build_succeeds.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/merge_when_pipeline_succeeds.md'
+---
+
This document was moved to [merge_when_pipeline_succeeds](../user/project/merge_requests/merge_when_pipeline_succeeds.md).
diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md
index 69eb6b286b0..18dc15f7327 100644
--- a/doc/workflow/milestones.md
+++ b/doc/workflow/milestones.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/milestones/index.md'
+---
+
This document was moved to [another location](../user/project/milestones/index.md).
diff --git a/doc/workflow/project_features.md b/doc/workflow/project_features.md
index feb88712f5a..f54afb768a1 100644
--- a/doc/workflow/project_features.md
+++ b/doc/workflow/project_features.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/index.md'
+---
+
This document was moved to [../user/project/index.md](../user/project/index.md)
diff --git a/doc/workflow/protected_branches.md b/doc/workflow/protected_branches.md
index ced7d391ace..1bcac4a2de5 100644
--- a/doc/workflow/protected_branches.md
+++ b/doc/workflow/protected_branches.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/protected_branches.md'
+---
+
This document was moved to [another location](../user/project/protected_branches.md).
diff --git a/doc/workflow/revert_changes.md b/doc/workflow/revert_changes.md
index cf1292253fc..15f199af703 100644
--- a/doc/workflow/revert_changes.md
+++ b/doc/workflow/revert_changes.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/revert_changes.md'
+---
+
This document was moved to [user/project/merge_requests/revert_changes](../user/project/merge_requests/revert_changes.md).
diff --git a/doc/workflow/share_projects_with_other_groups.md b/doc/workflow/share_projects_with_other_groups.md
index 2eb4d24958a..c39cd78f32d 100644
--- a/doc/workflow/share_projects_with_other_groups.md
+++ b/doc/workflow/share_projects_with_other_groups.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/members/share_project_with_groups.md'
+---
+
This document was moved to [../user/project/members/share_project_with_groups.md](../user/project/members/share_project_with_groups.md)
diff --git a/doc/workflow/share_with_group.md b/doc/workflow/share_with_group.md
index 2eb4d24958a..c39cd78f32d 100644
--- a/doc/workflow/share_with_group.md
+++ b/doc/workflow/share_with_group.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/members/share_project_with_groups.md'
+---
+
This document was moved to [../user/project/members/share_project_with_groups.md](../user/project/members/share_project_with_groups.md)
diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md
index 595c7da155b..2366372d984 100644
--- a/doc/workflow/web_editor.md
+++ b/doc/workflow/web_editor.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/repository/web_editor.md'
+---
+
This document was moved to [user/project/repository/web_editor](../user/project/repository/web_editor.md).
diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md
index abb8002f442..020455dcbdc 100644
--- a/doc/workflow/wip_merge_requests.md
+++ b/doc/workflow/wip_merge_requests.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/merge_requests/work_in_progress_merge_requests.md'
+---
+
This document was moved to [user/project/merge_requests/work_in_progress_merge_requests](../user/project/merge_requests/work_in_progress_merge_requests.md).
diff --git a/jest.config.js b/jest.config.js
index 1f6e04390ae..c7518be9e96 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,4 +1,4 @@
-/* eslint-disable filenames/match-regex */
+const IS_EE = require('./config/helpers/is_ee_env');
const reporters = ['default'];
@@ -37,4 +37,8 @@ module.exports = {
},
transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui)/)'],
timers: 'fake',
+ testEnvironment: '<rootDir>/spec/frontend/environment.js',
+ testEnvironmentOptions: {
+ IS_EE,
+ },
};
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 07f529b01bb..5c98b0ad56c 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -34,11 +34,11 @@ module API
repository = user_project.repository
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
- branches = ::Kaminari.paginate_array(branches)
+ branches = paginate(::Kaminari.paginate_array(branches))
merged_branch_names = repository.merged_branch_names(branches.map(&:name))
present(
- paginate(branches),
+ branches,
with: Entities::Branch,
current_user: current_user,
project: user_project,
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 611523a2444..4533305bfd3 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -275,7 +275,7 @@ module API
expose :printing_merge_request_link_enabled
expose :merge_method
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
- options[:statistics] && Ability.allowed?(options[:current_user], :download_code, project)
+ options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
# rubocop: disable CodeReuse/ActiveRecord
@@ -1558,8 +1558,6 @@ module API
class Suggestion < Grape::Entity
expose :id
- expose :from_original_line
- expose :to_original_line
expose :from_line
expose :to_line
expose :appliable?, as: :appliable
@@ -1590,7 +1588,7 @@ module API
end
class Cluster < Grape::Entity
- expose :id, :name, :created_at
+ expose :id, :name, :created_at, :domain
expose :provider_type, :platform_type, :environment_scope, :cluster_type
expose :user, using: Entities::UserBasic
expose :platform_kubernetes, using: Entities::Platform::Kubernetes
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index cb0d6d96f29..9fcf476f537 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -20,8 +20,19 @@ module API
optional :share_with_group_lock, type: Boolean, desc: 'Prevent sharing a project with another group within this group'
end
+ if Gitlab.ee?
+ params :optional_params_ee do
+ optional :membership_lock, type: Boolean, desc: 'Prevent adding new members to project membership within this group'
+ optional :ldap_cn, type: String, desc: 'LDAP Common Name'
+ optional :ldap_access, type: Integer, desc: 'A valid access level'
+ optional :shared_runners_minutes_limit, type: Integer, desc: '(admin-only) Pipeline minutes quota for this group'
+ all_or_none_of :ldap_cn, :ldap_access
+ end
+ end
+
params :optional_params do
use :optional_params_ce
+ use :optional_params_ee if Gitlab.ee?
end
params :statistics_params do
@@ -164,6 +175,10 @@ module API
optional :name, type: String, desc: 'The name of the group'
optional :path, type: String, desc: 'The path of the group'
use :optional_params
+
+ if Gitlab.ee?
+ optional :file_template_project_id, type: Integer, desc: 'The ID of a project to use for custom templates in this group'
+ end
end
put ':id' do
group = find_group!(params[:id])
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index b8bd180bdc1..8a21d44b4bf 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -302,6 +302,12 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ # rubocop: disable CodeReuse/ActiveRecord
+ def filter_by_title(items, title)
+ items.where(title: title)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def filter_by_search(items, text)
items.search(text)
end
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index fe78049af87..3fd824877ae 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -5,9 +5,11 @@ module API
module InternalHelpers
attr_reader :redirected_path
- def wiki?
- set_project unless defined?(@wiki) # rubocop:disable Gitlab/ModuleWithInstanceVariables
- @wiki # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ delegate :wiki?, to: :repo_type
+
+ def repo_type
+ set_project unless defined?(@repo_type) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @repo_type # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def project
@@ -67,10 +69,10 @@ module API
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_project
if params[:gl_repository]
- @project, @wiki = Gitlab::GlRepository.parse(params[:gl_repository])
+ @project, @repo_type = Gitlab::GlRepository.parse(params[:gl_repository])
@redirected_path = nil
else
- @project, @wiki, @redirected_path = Gitlab::RepoPath.parse(params[:project])
+ @project, @repo_type, @redirected_path = Gitlab::RepoPath.parse(params[:project])
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
@@ -78,7 +80,7 @@ module API
# Project id to pass between components that don't share/don't have
# access to the same filesystem mounts
def gl_repository
- Gitlab::GlRepository.gl_repository(project, wiki?)
+ repo_type.identifier_for_subject(project)
end
def gl_project_path
@@ -92,7 +94,7 @@ module API
# Return the repository depending on whether we want the wiki or the
# regular repository
def repository
- if wiki?
+ if repo_type.wiki?
project.wiki.repository
else
project.repository
diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb
new file mode 100644
index 00000000000..f6762910b0c
--- /dev/null
+++ b/lib/api/helpers/issues_helpers.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module IssuesHelpers
+ def self.update_params_at_least_one_of
+ [
+ :assignee_id,
+ :assignee_ids,
+ :confidential,
+ :created_at,
+ :description,
+ :discussion_locked,
+ :due_date,
+ :labels,
+ :milestone_id,
+ :state_event,
+ :title
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index e6a72b949f9..7b858dc2e72 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -31,11 +31,50 @@ module API
optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
end
+ if Gitlab.ee?
+ params :optional_project_params_ee do
+ optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins'
+ optional :approvals_before_merge, type: Integer, desc: 'How many approvers should approve merge request by default'
+ optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
+ optional :mirror, type: Boolean, desc: 'Enables pull mirroring in a project'
+ optional :mirror_trigger_builds, type: Boolean, desc: 'Pull mirroring triggers builds'
+ end
+ end
+
params :optional_project_params do
use :optional_project_params_ce
+ use :optional_project_params_ee if Gitlab.ee?
end
end
end
+
+ def self.update_params_at_least_one_of
+ [
+ :jobs_enabled,
+ :resolve_outdated_diff_discussions,
+ :ci_config_path,
+ :container_registry_enabled,
+ :default_branch,
+ :description,
+ :issues_enabled,
+ :lfs_enabled,
+ :merge_requests_enabled,
+ :merge_method,
+ :name,
+ :only_allow_merge_if_all_discussions_are_resolved,
+ :only_allow_merge_if_pipeline_succeeds,
+ :path,
+ :printing_merge_request_link_enabled,
+ :public_builds,
+ :request_access_enabled,
+ :shared_runners_enabled,
+ :snippets_enabled,
+ :tag_list,
+ :visibility,
+ :wiki_enabled,
+ :avatar
+ ]
+ end
end
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 7f4a00f1389..cb9aa849eeb 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -59,7 +59,7 @@ module API
actor
end
- access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
+ access_checker_klass = repo_type.access_checker_class
access_checker = access_checker_klass.new(actor, project,
protocol, authentication_abilities: ssh_authentication_abilities,
namespace_path: namespace_path, project_path: project_path,
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index b2ec4ed898e..fae20e45bf9 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -8,15 +8,6 @@ module API
helpers ::Gitlab::IssuableMetadata
- # EE::API::Issues would override the following helpers
- helpers do
- params :issues_params_ee do
- end
-
- params :issue_params_ee do
- end
- end
-
helpers do
# rubocop: disable CodeReuse/ActiveRecord
def find_issues(args = {})
@@ -33,6 +24,16 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ if Gitlab.ee?
+ params :issues_params_ee do
+ optional :weight, types: [Integer, String], integer_none_any: true, desc: 'The weight of the issue'
+ end
+
+ params :issue_params_ee do
+ optional :weight, type: Integer, desc: 'The weight of the issue'
+ end
+ end
+
params :issues_params do
optional :labels, type: Array[String], coerce_with: Validations::Types::LabelsList.coerce, desc: 'Comma-separated list of label names'
optional :milestone, type: String, desc: 'Milestone title'
@@ -57,7 +58,7 @@ module API
optional :confidential, type: Boolean, desc: 'Filter confidential or public issues'
use :pagination
- use :issues_params_ee
+ use :issues_params_ee if Gitlab.ee?
end
params :issue_params do
@@ -70,7 +71,7 @@ module API
optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential'
optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked"
- use :issue_params_ee
+ use :issue_params_ee if Gitlab.ee?
end
end
@@ -219,8 +220,8 @@ module API
desc: 'Date time when the issue was updated. Available only for admins and project owners.'
optional :state_event, type: String, values: %w[reopen close], desc: 'State of the issue'
use :issue_params
- at_least_one_of :title, :description, :assignee_ids, :assignee_id, :milestone_id, :discussion_locked,
- :labels, :created_at, :due_date, :confidential, :state_event
+
+ at_least_one_of(*Helpers::IssuesHelpers.update_params_at_least_one_of)
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/issues/:issue_iid' do
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
index a0ca39b69d4..62e159ab003 100644
--- a/lib/api/milestone_responses.rb
+++ b/lib/api/milestone_responses.rb
@@ -16,6 +16,7 @@ module API
optional :state, type: String, values: %w[active closed all], default: 'all',
desc: 'Return "active", "closed", or "all" milestones'
optional :iids, type: Array[Integer], desc: 'The IIDs of the milestones'
+ optional :title, type: String, desc: 'The title of the milestones'
optional :search, type: String, desc: 'The search criteria for the title or description of the milestone'
use :pagination
end
@@ -33,6 +34,7 @@ module API
milestones = parent.milestones.order_id_desc
milestones = Milestone.filter_by_state(milestones, params[:state])
milestones = filter_by_iid(milestones, params[:iids]) if params[:iids].present?
+ milestones = filter_by_title(milestones, params[:title]) if params[:title]
milestones = filter_by_search(milestones, params[:search]) if params[:search]
present paginate(milestones), with: Entities::Milestone
diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb
index c96261a7b57..b62ec887183 100644
--- a/lib/api/project_clusters.rb
+++ b/lib/api/project_clusters.rb
@@ -53,6 +53,7 @@ module API
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'
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'
@@ -83,6 +84,7 @@ module API
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'
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 22c90e4e83e..57336e95041 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -11,12 +11,20 @@ module API
before { authenticate_non_get! }
helpers do
- params :optional_filter_params_ee do
- # EE::API::Projects would override this helper
- end
+ if Gitlab.ee?
+ params :optional_filter_params_ee do
+ optional :wiki_checksum_failed, type: Grape::API::Boolean, default: false, desc: 'Limit by projects where wiki checksum is failed'
+ optional :repository_checksum_failed, type: Grape::API::Boolean, default: false, desc: 'Limit by projects where repository checksum is failed'
+ end
- params :optional_update_params_ee do
- # EE::API::Projects would override this helper
+ params :optional_update_params_ee do
+ optional :mirror_user_id, type: Integer, desc: 'User responsible for all the activity surrounding a pull mirror event'
+ optional :only_mirror_protected_branches, type: Grape::API::Boolean, desc: 'Only mirror protected branches'
+ optional :mirror_overwrites_diverged_branches, type: Grape::API::Boolean, desc: 'Pull mirror overwrites diverged branches'
+ optional :import_url, type: String, desc: 'URL from which the project is imported'
+ optional :packages_enabled, type: Grape::API::Boolean, desc: 'Enable project packages feature'
+ optional :fallback_approvals_required, type: Integer, desc: 'Overall approvals required when no rule is present'
+ end
end
# EE::API::Projects would override this method
@@ -35,34 +43,6 @@ module API
end
end
- def self.update_params_at_least_one_of
- [
- :jobs_enabled,
- :resolve_outdated_diff_discussions,
- :ci_config_path,
- :container_registry_enabled,
- :default_branch,
- :description,
- :issues_enabled,
- :lfs_enabled,
- :merge_requests_enabled,
- :merge_method,
- :name,
- :only_allow_merge_if_all_discussions_are_resolved,
- :only_allow_merge_if_pipeline_succeeds,
- :path,
- :printing_merge_request_link_enabled,
- :public_builds,
- :request_access_enabled,
- :shared_runners_enabled,
- :snippets_enabled,
- :tag_list,
- :visibility,
- :wiki_enabled,
- :avatar
- ]
- end
-
helpers do
params :statistics_params do
optional :statistics, type: Boolean, default: false, desc: 'Include project statistics'
@@ -97,7 +77,7 @@ module API
optional :with_programming_language, type: String, desc: 'Limit to repositories which use the given programming language'
optional :min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit by minimum access level of authenticated user'
- use :optional_filter_params_ee
+ use :optional_filter_params_ee if Gitlab.ee?
end
params :create_params do
@@ -316,8 +296,9 @@ module API
optional :path, type: String, desc: 'The path of the repository'
use :optional_project_params
+ use :optional_update_params_ee if Gitlab.ee?
- at_least_one_of(*::API::Projects.update_params_at_least_one_of)
+ at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of)
end
put ':id' do
authorize_admin_project
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index 5af43448727..f8cce1ed784 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -51,6 +51,30 @@ module API
optional :merge_access_level, type: Integer,
values: ProtectedBranch::MergeAccessLevel.allowed_access_levels,
desc: 'Access levels allowed to merge (defaults: `40`, maintainer access level)'
+
+ if Gitlab.ee?
+ optional :unprotect_access_level, type: Integer,
+ values: ProtectedBranch::UnprotectAccessLevel.allowed_access_levels,
+ desc: 'Access levels allowed to unprotect (defaults: `40`, maintainer access level)'
+
+ optional :allowed_to_push, type: Array, desc: 'An array of users/groups allowed to push' do
+ optional :access_level, type: Integer, values: ProtectedBranch::PushAccessLevel.allowed_access_levels
+ optional :user_id, type: Integer
+ optional :group_id, type: Integer
+ end
+
+ optional :allowed_to_merge, type: Array, desc: 'An array of users/groups allowed to merge' do
+ optional :access_level, type: Integer, values: ProtectedBranch::MergeAccessLevel.allowed_access_levels
+ optional :user_id, type: Integer
+ optional :group_id, type: Integer
+ end
+
+ optional :allowed_to_unprotect, type: Array, desc: 'An array of users/groups allowed to unprotect' do
+ optional :access_level, type: Integer, values: ProtectedBranch::UnprotectAccessLevel.allowed_access_levels
+ optional :user_id, type: Integer
+ optional :group_id, type: Integer
+ end
+ end
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/protected_branches' do
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 3cb2f69c4ef..d742c6c97c1 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -135,8 +135,44 @@ module API
desc: "Restrictions on the complexity of uploaded #{type.upcase} keys. A value of #{ApplicationSetting::FORBIDDEN_KEY_VALUE} disables all #{type.upcase} keys."
end
+ if Gitlab.ee?
+ optional :elasticsearch_aws, type: Boolean, desc: 'Enable support for AWS hosted elasticsearch'
+
+ given elasticsearch_aws: ->(val) { val } do
+ optional :elasticsearch_aws_access_key, type: String, desc: 'AWS IAM access key'
+ requires :elasticsearch_aws_region, type: String, desc: 'The AWS region the elasticsearch domain is configured'
+ optional :elasticsearch_aws_secret_access_key, type: String, desc: 'AWS IAM secret access key'
+ end
+
+ optional :elasticsearch_indexing, type: Boolean, desc: 'Enable Elasticsearch indexing'
+
+ given elasticsearch_indexing: ->(val) { val } do
+ optional :elasticsearch_search, type: Boolean, desc: 'Enable Elasticsearch search'
+ requires :elasticsearch_url, type: String, desc: 'The url to use for connecting to Elasticsearch. Use a comma-separated list to support clustering (e.g., "http://localhost:9200, http://localhost:9201")'
+ end
+
+ optional :email_additional_text, type: String, desc: 'Additional text added to the bottom of every email for legal/auditing/compliance reasons'
+ optional :help_text, type: String, desc: 'GitLab server administrator information'
+ optional :repository_size_limit, type: Integer, desc: 'Size limit per repository (MB)'
+ optional :file_template_project_id, type: Integer, desc: 'ID of project where instance-level file templates are stored.'
+ optional :repository_storages, type: Array[String], desc: 'A list of names of enabled storage paths, taken from `gitlab.yml`. New projects will be created in one of these stores, chosen at random.'
+ optional :snowplow_enabled, type: Boolean, desc: 'Enable Snowplow'
+
+ given snowplow_enabled: ->(val) { val } do
+ requires :snowplow_collector_uri, type: String, desc: 'Snowplow Collector URI'
+ optional :snowplow_cookie_domain, type: String, desc: 'Snowplow cookie domain'
+ optional :snowplow_site_id, type: String, desc: 'Snowplow Site/Application ID'
+ end
+
+ optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
+ end
+
optional_attributes = ::ApplicationSettingsHelper.visible_attributes << :performance_bar_allowed_group_id
+ if Gitlab.ee?
+ optional_attributes += EE::ApplicationSettingsHelper.possible_licensed_attributes
+ end
+
optional(*optional_attributes)
at_least_one_of(*optional_attributes)
end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index a3d4acc63d5..776329622e2 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -51,6 +51,10 @@ module API
optional :avatar, type: File, desc: 'Avatar image for user'
optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile'
all_or_none_of :extern_uid, :provider
+
+ if Gitlab.ee?
+ optional :shared_runners_minutes_limit, type: Integer, desc: 'Pipeline minutes quota for this user'
+ end
end
params :sort_params do
@@ -80,6 +84,10 @@ module API
use :sort_params
use :pagination
use :with_custom_attributes
+
+ if Gitlab.ee?
+ optional :skip_ldap, type: Boolean, default: false, desc: 'Skip LDAP users'
+ end
end
# rubocop: disable CodeReuse/ActiveRecord
get do
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index d0d81ebc870..3489ba827e4 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -55,6 +55,10 @@ module API
requires :key, type: String, desc: 'The key of the variable'
requires :value, type: String, desc: 'The value of the variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
+
+ if Gitlab.ee?
+ optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
+ end
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
@@ -76,6 +80,10 @@ module API
optional :key, type: String, desc: 'The key of the variable'
optional :value, type: String, desc: 'The value of the variable'
optional :protected, type: String, desc: 'Whether the variable is protected'
+
+ if Gitlab.ee?
+ optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
+ end
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 4764f8e1e19..5f8aca104aa 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -181,9 +181,10 @@ module Banzai
title = object_link_title(object, matches)
klass = reference_class(object_sym)
- data = data_attributes_for(link_content || match, parent, object,
- link_content: !!link_content,
- link_reference: link_reference)
+ data_attributes = data_attributes_for(link_content || match, parent, object,
+ link_content: !!link_content,
+ link_reference: link_reference)
+ data = data_attribute(data_attributes)
url =
if matches.names.include?("url") && matches[:url]
@@ -206,13 +207,13 @@ module Banzai
def data_attributes_for(text, parent, object, link_content: false, link_reference: false)
object_parent_type = parent.is_a?(Group) ? :group : :project
- data_attribute(
+ {
original: text,
link: link_content,
link_reference: link_reference,
object_parent_type => parent.id,
object_sym => object.id
- )
+ }
end
def object_link_text_extras(object, matches)
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 7098767b583..f05902078dc 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -20,7 +20,9 @@ module Banzai
end
def object_link_title(object, matches)
- object_link_commit_title(object, matches) || super
+ # The method will return `nil` if object is not a commit
+ # allowing for properly handling the extended MR Tooltip
+ object_link_commit_title(object, matches)
end
def object_link_text_extras(object, matches)
@@ -53,6 +55,14 @@ module Banzai
.includes(target_project: :namespace)
end
+ def reference_class(object_sym, options = {})
+ super(object_sym, tooltip: false)
+ end
+
+ def data_attributes_for(text, parent, object, data = {})
+ super.merge(project_path: parent.full_path, iid: object.iid, mr_title: object.title)
+ end
+
private
def object_link_commit_title(object, matches)
diff --git a/lib/banzai/suggestions_parser.rb b/lib/banzai/suggestions_parser.rb
index 09f36635020..0d7f751bfc1 100644
--- a/lib/banzai/suggestions_parser.rb
+++ b/lib/banzai/suggestions_parser.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+# TODO: Delete when https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26107
+# exchange this parser by `Gitlab::Diff::SuggestionsParser`.
module Banzai
module SuggestionsParser
# Returns the content of each suggestion code block.
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index f42ca5a9cd6..1204e53ee2e 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -24,7 +24,7 @@ module Gitlab
if File.exist?(root.join("REVISION"))
File.read(root.join("REVISION")).strip.freeze
else
- result = Gitlab::Popen.popen_with_detail(%W[#{config.git.bin_path} log --pretty=format:%h -n 1])
+ result = Gitlab::Popen.popen_with_detail(%W[#{config.git.bin_path} log --pretty=format:%h --abbrev=11 -n 1])
if result.status.success?
result.stdout.chomp.freeze
diff --git a/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
new file mode 100644
index 00000000000..a4c6540c61b
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_merge_request_assignees_table.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This background migration creates records on merge_request_assignees according
+ # to the given merge request IDs range. A _single_ INSERT is issued for the given range.
+ # This is required for supporting multiple assignees on merge requests.
+ class PopulateMergeRequestAssigneesTable
+ def perform(from_id, to_id)
+ select_sql =
+ MergeRequest
+ .where(merge_request_assignees_not_exists_clause)
+ .where(id: from_id..to_id)
+ .where('assignee_id IS NOT NULL')
+ .select(:id, :assignee_id)
+ .to_sql
+
+ execute("INSERT INTO merge_request_assignees (merge_request_id, user_id) #{select_sql}")
+ end
+
+ private
+
+ def merge_request_assignees_not_exists_clause
+ <<~SQL
+ NOT EXISTS (SELECT 1 FROM merge_request_assignees
+ WHERE merge_request_assignees.merge_request_id = merge_requests.id)
+ SQL
+ end
+
+ def execute(sql)
+ @connection ||= ActiveRecord::Base.connection
+ @connection.execute(sql)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index bf9f03f6134..03af99ba9a5 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -33,6 +33,13 @@ module Gitlab
end
end
+ def merge_request_ref_exists?
+ strong_memoize(:merge_request_ref_exists) do
+ MergeRequest.merge_request_ref?(origin_ref) &&
+ project.repository.ref_exists?(origin_ref)
+ end
+ end
+
def ref
strong_memoize(:ref) do
Gitlab::Git.ref_name(origin_ref)
diff --git a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
index ebd7e6e8289..aaa3daddcc5 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/abilities.rb
@@ -44,6 +44,8 @@ module Gitlab
access.can_update_branch?(@command.ref)
elsif @command.tag_exists?
access.can_create_tag?(@command.ref)
+ elsif @command.merge_request_ref_exists?
+ access.can_update_branch?(@command.merge_request.source_branch)
else
true # Allow it for now and we'll reject when we check ref existence
end
diff --git a/lib/gitlab/ci/pipeline/chain/validate/repository.rb b/lib/gitlab/ci/pipeline/chain/validate/repository.rb
index 9c6c2bc8e25..8f5445850d7 100644
--- a/lib/gitlab/ci/pipeline/chain/validate/repository.rb
+++ b/lib/gitlab/ci/pipeline/chain/validate/repository.rb
@@ -9,7 +9,7 @@ module Gitlab
include Chain::Helpers
def perform!
- unless @command.branch_exists? || @command.tag_exists?
+ unless @command.branch_exists? || @command.tag_exists? || @command.merge_request_ref_exists?
return error('Reference not found')
end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 7ec786b6d5d..d4c70d4b9da 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -50,8 +50,8 @@ variables:
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
POSTGRES_VERSION: 9.6.2
- KUBERNETES_VERSION: 1.11.7
- HELM_VERSION: 2.12.3
+ KUBERNETES_VERSION: 1.11.9
+ HELM_VERSION: 2.13.1
DOCKER_DRIVER: overlay2
@@ -531,8 +531,8 @@ rollout 100%:
touch clair-whitelist.yml
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
+ while( ! wget -T 10 -q -O /dev/null http://${DOCKER_SERVICE}: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_SERVICE}:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true
}
function code_quality() {
@@ -800,10 +800,15 @@ rollout 100%:
kubectl version --client
}
+ # With the Kubernetes executor, 'localhost' must be used instead
+ # https://docs.gitlab.com/runner/executors/kubernetes.html
function setup_docker() {
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
export DOCKER_HOST='tcp://localhost:2375'
+ export DOCKER_SERVICE="localhost"
+ else
+ export DOCKER_SERVICE="docker"
fi
fi
}
@@ -856,7 +861,7 @@ rollout 100%:
function check_kube_domain() {
ensure_kube_ingress_base_domain
- if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then
+ if [[ -z "$KUBE_INGRESS_BASE_DOMAIN" ]]; then
echo "In order to deploy or use Review Apps,"
echo "AUTO_DEVOPS_DOMAIN or KUBE_INGRESS_BASE_DOMAIN variables must be set"
echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings"
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 ea1e6ae5fdc..0b7a531682b 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -17,23 +17,30 @@ container_scanning:
#
# Container Scanning deals with Docker images only so no need to import the project's Git repository:
GIT_STRATEGY: none
+ # Services and containers running in the same Kubernetes pod are all sharing the same localhost address
+ # https://docs.gitlab.com/runner/executors/kubernetes.html
+ DOCKER_SERVICE: docker
+ DOCKER_HOST: tcp://${DOCKER_SERVICE}:2375/
+ # https://hub.docker.com/r/arminc/clair-local-scan/tags
+ CLAIR_LOCAL_SCAN_VERSION: v2.0.6
allow_failure: true
services:
- docker:stable-dind
script:
+ - if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then { export DOCKER_SERVICE="localhost" ; export DOCKER_HOST="tcp://${DOCKER_SERVICE}:2375" ; } fi
- 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:v2.0.6
+ - 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
+ - while( ! wget -q -O /dev/null http://${DOCKER_SERVICE}: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
+ - while( ! wget -T 10 -q -O /dev/null http://${DOCKER_SERVICE}: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_SERVICE}: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
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 552aad83dd4..469a7fd9f7b 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -47,7 +47,7 @@ module Gitlab
# defaults for missing columns.
if ActiveRecord::Migrator.needs_migration?
db_attributes = current_settings&.attributes || {}
- ::ApplicationSetting.build_from_defaults(db_attributes)
+ fake_application_settings(db_attributes)
elsif current_settings.present?
current_settings
else
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index 0d76e324d10..ac65cf74808 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -108,7 +108,23 @@ module Gitlab
%r{\A(ee/)?public/} => :frontend,
%r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
%r{\A(ee/)?vendor/assets/} => :frontend,
- %r{\A(jest\.config\.js|package\.json|yarn\.lock|\.eslintrc\.yml)\z} => :frontend,
+ %r{\Ascripts/frontend/} => :frontend,
+ %r{(\A|/)(
+ \.babelrc |
+ \.eslintignore |
+ \.eslintrc(\.yml)? |
+ \.nvmrc |
+ \.prettierignore |
+ \.prettierrc |
+ \.scss-lint.yml |
+ \.stylelintrc |
+ babel\.config\.js |
+ jest\.config\.js |
+ karma\.config\.js |
+ webpack\.config\.js |
+ package\.json |
+ yarn\.lock
+ )\z}x => :frontend,
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index dce80bf21de..c46087e65de 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -133,11 +133,15 @@ module Gitlab
end
def new_blob
- new_blob_lazy&.itself
+ strong_memoize(:new_blob) do
+ new_blob_lazy&.itself
+ end
end
def old_blob
- old_blob_lazy&.itself
+ strong_memoize(:old_blob) do
+ old_blob_lazy&.itself
+ end
end
def new_blob_lines_between(from_line, to_line)
diff --git a/lib/gitlab/diff/suggestion.rb b/lib/gitlab/diff/suggestion.rb
new file mode 100644
index 00000000000..027c7a31bcf
--- /dev/null
+++ b/lib/gitlab/diff/suggestion.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class Suggestion
+ include Suggestible
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :diff_file, :lines_above, :lines_below,
+ :target_line
+
+ def initialize(text, line:, above:, below:, diff_file:)
+ @text = text
+ @target_line = line
+ @lines_above = above.to_i
+ @lines_below = below.to_i
+ @diff_file = diff_file
+ end
+
+ def to_hash
+ {
+ from_content: from_content,
+ to_content: to_content,
+ lines_above: @lines_above,
+ lines_below: @lines_below
+ }
+ end
+
+ def from_content
+ strong_memoize(:from_content) do
+ fetch_from_content
+ end
+ end
+
+ def to_content
+ # The parsed suggestion doesn't have information about the correct
+ # ending characters (we may have a line break, or not), so we take
+ # this information from the last line being changed (last
+ # characters).
+ endline_chars = line_break_chars(from_content.lines.last)
+ "#{@text}#{endline_chars}"
+ end
+
+ private
+
+ def line_break_chars(line)
+ match = /\r\n|\r|\n/.match(line)
+ match[0] if match
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
index 043bd9a4bcb..c8c03d5d001 100644
--- a/lib/gitlab/diff/suggestions_parser.rb
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -5,6 +5,47 @@ module Gitlab
class SuggestionsParser
# Matches for instance "-1", "+1" or "-1+2".
SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze
+
+ class << self
+ # Returns an array of Gitlab::Diff::Suggestion which represents each
+ # suggestion in the given text.
+ #
+ def parse(text, position:, project:)
+ return [] unless position.complete?
+
+ html = Banzai.render(text, project: nil, no_original_data: true)
+ doc = Nokogiri::HTML(html)
+ suggestion_nodes = doc.search('pre.suggestion')
+
+ return [] if suggestion_nodes.empty?
+
+ diff_file = position.diff_file(project.repository)
+
+ suggestion_nodes.map do |node|
+ lang_param = node['data-lang-params']
+
+ lines_above, lines_below = nil
+
+ if lang_param && suggestion_params = fetch_suggestion_params(lang_param)
+ lines_above, lines_below =
+ suggestion_params[:above],
+ suggestion_params[:below]
+ end
+
+ Gitlab::Diff::Suggestion.new(node.text,
+ line: position.new_line,
+ above: lines_above.to_i,
+ below: lines_below.to_i,
+ diff_file: diff_file)
+ end
+ end
+
+ private
+
+ def fetch_suggestion_params(lang_param)
+ lang_param.match(SUGGESTION_CONTEXT)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index a4a154c80f7..5a61a7f5d60 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -76,8 +76,11 @@ module Gitlab
str.dup.force_encoding(Encoding::ASCII_8BIT)
end
- def binary_stringio(str)
- StringIO.new(str.freeze || '').tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
+ def binary_io(str_or_io)
+ io = str_or_io.to_io.dup if str_or_io.respond_to?(:to_io)
+ io ||= StringIO.new(str_or_io.to_s.freeze)
+
+ io.tap { |io| io.set_encoding(Encoding::ASCII_8BIT) }
end
private
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 88ff9fbceb4..8fac3621df9 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -318,6 +318,10 @@ module Gitlab
parent_ids.size > 1
end
+ def gitaly_commit?
+ raw_commit.is_a?(Gitaly::GitCommit)
+ end
+
def tree_entry(path)
return unless path.present?
@@ -340,7 +344,7 @@ module Gitlab
end
def to_gitaly_commit
- return raw_commit if raw_commit.is_a?(Gitaly::GitCommit)
+ return raw_commit if gitaly_commit?
message_split = raw_commit.message.split("\n", 2)
Gitaly::GitCommit.new(
diff --git a/lib/gitlab/git/pre_receive_error.rb b/lib/gitlab/git/pre_receive_error.rb
index 03caace6fce..b46d4ba0b02 100644
--- a/lib/gitlab/git/pre_receive_error.rb
+++ b/lib/gitlab/git/pre_receive_error.rb
@@ -4,19 +4,38 @@ module Gitlab
module Git
#
# PreReceiveError is special because its message gets displayed to users
- # in the web UI. To prevent XSS we sanitize the message on
- # initialization.
+ # in the web UI. Because of this, we:
+ # - Only display errors that have been marked as safe with a prefix.
+ # This is to prevent leaking of stacktraces, or other sensitive info.
+ # - Sanitize the string of any XSS
class PreReceiveError < StandardError
- def initialize(msg = '')
- super(nlbr(msg))
+ SAFE_MESSAGE_PREFIXES = [
+ 'GitLab:', # Messages from gitlab-shell
+ 'GL-HOOK-ERR:' # Messages marked as safe by user
+ ].freeze
+
+ SAFE_MESSAGE_REGEX = /^(#{SAFE_MESSAGE_PREFIXES.join('|')})\s*(?<safe_message>.+)/
+
+ def initialize(message = '')
+ super(sanitize(message))
end
private
# In gitaly-ruby we override this method to do nothing, so that
# sanitization happens in gitlab-rails only.
- def nlbr(str)
- Gitlab::Utils.nlbr(str)
+ def sanitize(message)
+ return message if message.blank?
+
+ safe_messages = message.split("\n").map do |msg|
+ if (match = msg.match(SAFE_MESSAGE_REGEX))
+ match[:safe_message].presence
+ end
+ end
+
+ safe_messages = safe_messages.compact.join("\n")
+
+ Gitlab::Utils.nlbr(safe_messages)
end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index e3b9a7a1a89..35565b68388 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -33,12 +33,6 @@ module Gitlab
MUTEX = Mutex.new
- class << self
- attr_accessor :query_time
- end
-
- self.query_time = 0
-
define_histogram :gitaly_controller_action_duration_seconds do
docstring "Gitaly endpoint histogram by controller and action combination"
base_labels Gitlab::Metrics::Transaction::BASE_LABELS.merge(gitaly_service: nil, rpc: nil)
@@ -174,6 +168,18 @@ module Gitlab
add_call_details(feature: "#{service}##{rpc}", duration: duration, request: request_hash, rpc: rpc)
end
+ def self.query_time
+ SafeRequestStore[:gitaly_query_time] ||= 0
+ end
+
+ def self.query_time=(duration)
+ SafeRequestStore[:gitaly_query_time] = duration
+ end
+
+ def self.query_time_ms
+ (self.query_time * 1000).round(2)
+ end
+
def self.current_transaction_labels
Gitlab::Metrics::Transaction.current&.labels || {}
end
@@ -226,7 +232,7 @@ module Gitlab
result
end
- SERVER_FEATURE_FLAGS = %w[go-find-all-tags].freeze
+ SERVER_FEATURE_FLAGS = %w[].freeze
def self.server_feature_flags
SERVER_FEATURE_FLAGS.map do |f|
@@ -296,6 +302,26 @@ module Gitlab
end
end
+ # Normally a FindCommit RPC will cache the commit with its SHA
+ # instead of a ref name, since it's possible the branch is mutated
+ # afterwards. However, for read-only requests that never mutate the
+ # branch, this method allows caching of the ref name directly.
+ def self.allow_ref_name_caching
+ return yield unless Gitlab::SafeRequestStore.active?
+ return yield if ref_name_caching_allowed?
+
+ begin
+ Gitlab::SafeRequestStore[:allow_ref_name_caching] = true
+ yield
+ ensure
+ Gitlab::SafeRequestStore[:allow_ref_name_caching] = false
+ end
+ end
+
+ def self.ref_name_caching_allowed?
+ Gitlab::SafeRequestStore[:allow_ref_name_caching]
+ end
+
def self.get_call_count(key)
Gitlab::SafeRequestStore[key] || 0
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index ea12424eb4a..0d5debfcd01 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -286,7 +286,7 @@ module Gitlab
commit = call_find_commit(revision)
return unless commit
- key[:commit_id] = commit.id
+ key[:commit_id] = commit.id unless GitalyClient.ref_name_caching_allowed?
Gitlab::SafeRequestStore[key] = commit
else
call_find_commit(revision)
diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb
index 6304f998563..077b63205a8 100644
--- a/lib/gitlab/gitaly_client/conflicts_service.rb
+++ b/lib/gitlab/gitaly_client/conflicts_service.rb
@@ -37,7 +37,7 @@ module Gitlab
end
def resolve_conflicts(target_repository, resolution, source_branch, target_branch)
- reader = binary_stringio(resolution.files.to_json)
+ reader = binary_io(resolution.files.to_json)
req_enum = Enumerator.new do |y|
header = resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch)
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 2528208440e..b0f328ce3d4 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -294,7 +294,7 @@ module Gitlab
action: Gitaly::UserCommitFilesAction.new(header: action_header)
)
- reader = binary_stringio(action[:content])
+ reader = binary_io(action[:content])
until reader.eof?
chunk = reader.read(MAX_MSG_SIZE)
@@ -327,7 +327,7 @@ module Gitlab
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
target_branch: encode_binary(branch_name)
)
- reader = binary_stringio(patches)
+ reader = binary_io(patches)
chunks = Enumerator.new do |chunk|
chunk.yield Gitaly::UserApplyPatchRequest.new(header: header)
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index 2b3d622af4d..15c9463e2f2 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -22,7 +22,7 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details)
)
- strio = binary_stringio(content)
+ strio = binary_io(content)
enum = Enumerator.new do |y|
until strio.eof?
@@ -49,7 +49,7 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details)
)
- strio = binary_stringio(content)
+ strio = binary_io(content)
enum = Enumerator.new do |y|
until strio.eof?
diff --git a/lib/gitlab/gl_repository.rb b/lib/gitlab/gl_repository.rb
index 435b74806e7..c2be7f3d63a 100644
--- a/lib/gitlab/gl_repository.rb
+++ b/lib/gitlab/gl_repository.rb
@@ -2,23 +2,38 @@
module Gitlab
module GlRepository
- def self.gl_repository(project, is_wiki)
- "#{is_wiki ? 'wiki' : 'project'}-#{project.id}"
+ PROJECT = RepoType.new(
+ name: :project,
+ access_checker_class: Gitlab::GitAccess,
+ repository_accessor: -> (project) { project.repository }
+ ).freeze
+ WIKI = RepoType.new(
+ name: :wiki,
+ access_checker_class: Gitlab::GitAccessWiki,
+ repository_accessor: -> (project) { project.wiki.repository }
+ ).freeze
+
+ TYPES = {
+ PROJECT.name.to_s => PROJECT,
+ WIKI.name.to_s => WIKI
+ }.freeze
+
+ def self.types
+ TYPES
end
- # rubocop: disable CodeReuse/ActiveRecord
def self.parse(gl_repository)
- match_data = /\A(project|wiki)-([1-9][0-9]*)\z/.match(gl_repository)
- unless match_data
+ type_name, _id = gl_repository.split('-').first
+ type = types[type_name]
+ subject_id = type&.fetch_id(gl_repository)
+
+ unless subject_id
raise ArgumentError, "Invalid GL Repository \"#{gl_repository}\""
end
- type, id = match_data.captures
- project = Project.find_by(id: id)
- wiki = type == 'wiki'
+ project = Project.find_by_id(subject_id)
- [project, wiki]
+ [project, type]
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/lib/gitlab/gl_repository/repo_type.rb b/lib/gitlab/gl_repository/repo_type.rb
new file mode 100644
index 00000000000..7abe6c29a25
--- /dev/null
+++ b/lib/gitlab/gl_repository/repo_type.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GlRepository
+ class RepoType
+ attr_reader :name,
+ :access_checker_class,
+ :repository_accessor
+
+ def initialize(name:, access_checker_class:, repository_accessor:)
+ @name = name
+ @access_checker_class = access_checker_class
+ @repository_accessor = repository_accessor
+ end
+
+ def identifier_for_subject(subject)
+ "#{name}-#{subject.id}"
+ end
+
+ def fetch_id(identifier)
+ match = /\A#{name}-(?<id>\d+)\z/.match(identifier)
+ match[:id] if match
+ end
+
+ def wiki?
+ self == WIKI
+ end
+
+ def project?
+ self == PROJECT
+ end
+
+ def path_suffix
+ project? ? "" : ".#{name}"
+ end
+
+ def repository_for(subject)
+ repository_accessor.call(subject)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/grape_logging/loggers/perf_logger.rb b/lib/gitlab/grape_logging/loggers/perf_logger.rb
index e3b9c59bd6e..18ea3a8d2f3 100644
--- a/lib/gitlab/grape_logging/loggers/perf_logger.rb
+++ b/lib/gitlab/grape_logging/loggers/perf_logger.rb
@@ -6,7 +6,10 @@ module Gitlab
module Loggers
class PerfLogger < ::GrapeLogging::Loggers::Base
def parameters(_, _)
- { gitaly_calls: Gitlab::GitalyClient.get_request_count }
+ {
+ gitaly_calls: Gitlab::GitalyClient.get_request_count,
+ gitaly_duration: Gitlab::GitalyClient.query_time_ms
+ }
end
end
end
diff --git a/lib/gitlab/import/merge_request_helpers.rb b/lib/gitlab/import/merge_request_helpers.rb
index fa3ff6c3f12..b3fe1fc0685 100644
--- a/lib/gitlab/import/merge_request_helpers.rb
+++ b/lib/gitlab/import/merge_request_helpers.rb
@@ -38,7 +38,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def insert_or_replace_git_data(merge_request, source_branch_sha, target_branch_sha, already_exists = false)
# These fields are set so we can create the correct merge request
# diffs.
@@ -47,24 +46,21 @@ module Gitlab
merge_request.keep_around_commit
+ # We force to recreate all diffs to replace all existing data
+ # We use `.all` as otherwise `dependent: :nullify` (the default)
+ # takes an effect
+ merge_request.merge_request_diffs.all.delete_all if already_exists
+
# MR diffs normally use an "after_save" hook to pull data from Git.
# All of this happens in the transaction started by calling
# create/save/etc. This in turn can lead to these transactions being
# held open for much longer than necessary. To work around this we
# first save the diff, then populate it.
- diff =
- if already_exists
- merge_request.merge_request_diffs.take ||
- merge_request.merge_request_diffs.build
- else
- merge_request.merge_request_diffs.build
- end
-
+ diff = merge_request.merge_request_diffs.build
diff.importing = true
diff.save
diff.save_git_content
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index d5a291dfa78..ce268793128 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -33,6 +33,7 @@ project_tree:
- :user
- merge_requests:
- :metrics
+ - :suggestions
- notes:
- :author
- events:
diff --git a/lib/gitlab/quick_actions/command_definition.rb b/lib/gitlab/quick_actions/command_definition.rb
index e7bfcb16582..93030fd454e 100644
--- a/lib/gitlab/quick_actions/command_definition.rb
+++ b/lib/gitlab/quick_actions/command_definition.rb
@@ -4,7 +4,7 @@ module Gitlab
module QuickActions
class CommandDefinition
attr_accessor :name, :aliases, :description, :explanation, :params,
- :condition_block, :parse_params_block, :action_block, :warning
+ :condition_block, :parse_params_block, :action_block, :warning, :types
def initialize(name, attributes = {})
@name = name
@@ -17,6 +17,7 @@ module Gitlab
@condition_block = attributes[:condition_block]
@parse_params_block = attributes[:parse_params_block]
@action_block = attributes[:action_block]
+ @types = attributes[:types] || []
end
def all_names
@@ -28,6 +29,7 @@ module Gitlab
end
def available?(context)
+ return false unless valid_type?(context)
return true unless condition_block
context.instance_exec(&condition_block)
@@ -96,6 +98,10 @@ module Gitlab
context.instance_exec(arg, &parse_params_block)
end
+
+ def valid_type?(context)
+ types.blank? || types.any? { |type| context.quick_action_target.is_a?(type) }
+ end
end
end
end
diff --git a/lib/gitlab/quick_actions/commit_actions.rb b/lib/gitlab/quick_actions/commit_actions.rb
new file mode 100644
index 00000000000..62c0fbb5afd
--- /dev/null
+++ b/lib/gitlab/quick_actions/commit_actions.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module CommitActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # Commit only quick actions definitions
+ desc 'Tag this commit.'
+ explanation do |tag_name, message|
+ with_message = %{ with "#{message}"} if message.present?
+ "Tags this commit to #{tag_name}#{with_message}."
+ end
+ params 'v1.2.3 <message>'
+ parse_params do |tag_name_and_message|
+ tag_name_and_message.split(' ', 2)
+ end
+ types Commit
+ condition do
+ current_user.can?(:push_code, project)
+ end
+ command :tag do |tag_name, message|
+ @updates[:tag_name] = tag_name
+ @updates[:tag_message] = message
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/common_actions.rb b/lib/gitlab/quick_actions/common_actions.rb
new file mode 100644
index 00000000000..5d2732d4826
--- /dev/null
+++ b/lib/gitlab/quick_actions/common_actions.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module CommonActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # This is a dummy command, so that it appears in the autocomplete commands
+ desc 'CC'
+ params '@user'
+ command :cc
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/dsl.rb b/lib/gitlab/quick_actions/dsl.rb
index a3aab92061b..ecb2169151e 100644
--- a/lib/gitlab/quick_actions/dsl.rb
+++ b/lib/gitlab/quick_actions/dsl.rb
@@ -24,7 +24,7 @@ module Gitlab
# Example:
#
# desc do
- # "This is a dynamic description for #{noteable.to_ability_name}"
+ # "This is a dynamic description for #{quick_action_target.to_ability_name}"
# end
# command :command_key do |arguments|
# # Awesome code block
@@ -66,6 +66,23 @@ module Gitlab
@explanation = block_given? ? block : text
end
+ # Allows to define type(s) that must be met in order for the command
+ # to be returned by `.command_names` & `.command_definitions`.
+ #
+ # It is being evaluated before the conditions block is being evaluated
+ #
+ # If no types are passed then any type is allowed as the check is simply skipped.
+ #
+ # Example:
+ #
+ # types Commit, Issue, MergeRequest
+ # command :command_key do |arguments|
+ # # Awesome code block
+ # end
+ def types(*types_list)
+ @types = types_list
+ end
+
# Allows to define conditions that must be met in order for the command
# to be returned by `.command_names` & `.command_definitions`.
# It accepts a block that will be evaluated with the context
@@ -144,7 +161,8 @@ module Gitlab
params: @params,
condition_block: @condition_block,
parse_params_block: @parse_params_block,
- action_block: block
+ action_block: block,
+ types: @types
)
self.command_definitions << definition
@@ -159,6 +177,7 @@ module Gitlab
@condition_block = nil
@warning = nil
@parse_params_block = nil
+ @types = nil
end
end
end
diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb
new file mode 100644
index 00000000000..ad2e15d19fa
--- /dev/null
+++ b/lib/gitlab/quick_actions/issuable_actions.rb
@@ -0,0 +1,221 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module IssuableActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ SHRUG = '¯\\_(ツ)_/¯'.freeze
+ TABLEFLIP = '(╯°□°)╯︵ ┻━┻'.freeze
+
+ included do
+ # Issue, MergeRequest, Epic: quick actions definitions
+ desc do
+ "Close this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
+ end
+ explanation do
+ "Closes this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ end
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.open? &&
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :close do
+ @updates[:state_event] = 'close'
+ end
+
+ desc do
+ "Reopen this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
+ end
+ explanation do
+ "Reopens this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ end
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.closed? &&
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :reopen do
+ @updates[:state_event] = 'reopen'
+ end
+
+ desc 'Change title'
+ explanation do |title_param|
+ "Changes the title to \"#{title_param}\"."
+ end
+ params '<New title>'
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :title do |title_param|
+ @updates[:title] = title_param
+ end
+
+ desc 'Add label(s)'
+ explanation do |labels_param|
+ labels = find_label_references(labels_param)
+
+ "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ end
+ params '~label1 ~"label 2"'
+ types Issuable
+ condition do
+ parent &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent) &&
+ find_labels.any?
+ end
+ command :label do |labels_param|
+ label_ids = find_label_ids(labels_param)
+
+ if label_ids.any?
+ @updates[:add_label_ids] ||= []
+ @updates[:add_label_ids] += label_ids
+
+ @updates[:add_label_ids].uniq!
+ end
+ end
+
+ desc 'Remove all or specific label(s)'
+ explanation do |labels_param = nil|
+ if labels_param.present?
+ labels = find_label_references(labels_param)
+ "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ else
+ 'Removes all labels.'
+ end
+ end
+ params '~label1 ~"label 2"'
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.labels.any? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent)
+ end
+ command :unlabel do |labels_param = nil|
+ if labels_param.present?
+ label_ids = find_label_ids(labels_param)
+
+ if label_ids.any?
+ @updates[:remove_label_ids] ||= []
+ @updates[:remove_label_ids] += label_ids
+
+ @updates[:remove_label_ids].uniq!
+ end
+ else
+ @updates[:label_ids] = []
+ end
+ end
+
+ desc 'Replace all label(s)'
+ explanation do |labels_param|
+ labels = find_label_references(labels_param)
+ "Replaces all labels with #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ end
+ params '~label1 ~"label 2"'
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.labels.any? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent)
+ end
+ command :relabel do |labels_param|
+ label_ids = find_label_ids(labels_param)
+
+ if label_ids.any?
+ @updates[:label_ids] ||= []
+ @updates[:label_ids] += label_ids
+
+ @updates[:label_ids].uniq!
+ end
+ end
+
+ desc 'Add a todo'
+ explanation 'Adds a todo.'
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ !TodoService.new.todo_exist?(quick_action_target, current_user)
+ end
+ command :todo do
+ @updates[:todo_event] = 'add'
+ end
+
+ desc 'Mark todo as done'
+ explanation 'Marks todo as done.'
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ TodoService.new.todo_exist?(quick_action_target, current_user)
+ end
+ command :done do
+ @updates[:todo_event] = 'done'
+ end
+
+ desc 'Subscribe'
+ explanation do
+ "Subscribes to this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ end
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ !quick_action_target.subscribed?(current_user, project)
+ end
+ command :subscribe do
+ @updates[:subscription_event] = 'subscribe'
+ end
+
+ desc 'Unsubscribe'
+ explanation do
+ "Unsubscribes from this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ end
+ types Issuable
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.subscribed?(current_user, project)
+ end
+ command :unsubscribe do
+ @updates[:subscription_event] = 'unsubscribe'
+ end
+
+ desc 'Toggle emoji award'
+ explanation do |name|
+ "Toggles :#{name}: emoji award." if name
+ end
+ params ':emoji:'
+ types Issuable
+ condition do
+ quick_action_target.persisted?
+ end
+ parse_params do |emoji_param|
+ match = emoji_param.match(Banzai::Filter::EmojiFilter.emoji_pattern)
+ match[1] if match
+ end
+ command :award do |name|
+ if name && quick_action_target.user_can_award?(current_user)
+ @updates[:emoji_award] = name
+ end
+ end
+
+ desc "Append the comment with #{SHRUG}"
+ params '<Comment>'
+ types Issuable
+ substitution :shrug do |comment|
+ "#{comment} #{SHRUG}"
+ end
+
+ desc "Append the comment with #{TABLEFLIP}"
+ params '<Comment>'
+ types Issuable
+ substitution :tableflip do |comment|
+ "#{comment} #{TABLEFLIP}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
new file mode 100644
index 00000000000..1f08e8740a2
--- /dev/null
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -0,0 +1,136 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module IssueActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # Issue only quick actions definition
+ desc 'Set due date'
+ explanation do |due_date|
+ "Sets the due date to #{due_date.to_s(:medium)}." if due_date
+ end
+ params '<in 2 days | this Friday | December 31st>'
+ types Issue
+ condition do
+ quick_action_target.respond_to?(:due_date) &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ parse_params do |due_date_param|
+ Chronic.parse(due_date_param).try(:to_date)
+ end
+ command :due do |due_date|
+ @updates[:due_date] = due_date if due_date
+ end
+
+ desc 'Remove due date'
+ explanation 'Removes the due date.'
+ types Issue
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.respond_to?(:due_date) &&
+ quick_action_target.due_date? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ command :remove_due_date do
+ @updates[:due_date] = nil
+ end
+
+ desc 'Move issue from one column of the board to another'
+ explanation do |target_list_name|
+ label = find_label_references(target_list_name).first
+ "Moves issue to #{label} column in the board." if label
+ end
+ params '~"Target column"'
+ types Issue
+ condition do
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) &&
+ quick_action_target.project.boards.count == 1
+ end
+ # rubocop: disable CodeReuse/ActiveRecord
+ command :board_move do |target_list_name|
+ label_ids = find_label_ids(target_list_name)
+
+ if label_ids.size == 1
+ label_id = label_ids.first
+
+ # Ensure this label corresponds to a list on the board
+ next unless Label.on_project_boards(quick_action_target.project_id).where(id: label_id).exists?
+
+ @updates[:remove_label_ids] =
+ quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id)
+ @updates[:add_label_ids] = [label_id]
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ desc 'Mark this issue as a duplicate of another issue'
+ explanation do |duplicate_reference|
+ "Marks this issue as a duplicate of #{duplicate_reference}."
+ end
+ params '#issue'
+ types Issue
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :duplicate do |duplicate_param|
+ canonical_issue = extract_references(duplicate_param, :issue).first
+
+ if canonical_issue.present?
+ @updates[:canonical_issue_id] = canonical_issue.id
+ end
+ end
+
+ desc 'Move this issue to another project.'
+ explanation do |path_to_project|
+ "Moves this issue to #{path_to_project}."
+ end
+ params 'path/to/project'
+ types Issue
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ command :move do |target_project_path|
+ target_project = Project.find_by_full_path(target_project_path)
+
+ if target_project.present?
+ @updates[:target_project] = target_project
+ end
+ end
+
+ desc 'Make issue confidential.'
+ explanation do
+ 'Makes this issue confidential'
+ end
+ types Issue
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :confidential do
+ @updates[:confidential] = true
+ end
+
+ desc 'Create a merge request.'
+ explanation do |branch_name = nil|
+ branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch'
+ "Creates #{branch_text} and a merge request to resolve this issue"
+ end
+ params "<branch name>"
+ types Issue
+ condition do
+ current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project)
+ end
+ command :create_merge_request do |branch_name = nil|
+ @updates[:create_merge_request] = {
+ branch_name: branch_name,
+ issue_iid: quick_action_target.iid
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
new file mode 100644
index 00000000000..08872eda410
--- /dev/null
+++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
@@ -0,0 +1,225 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module IssueAndMergeRequestActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # Issue, MergeRequest: quick actions definitions
+ desc 'Assign'
+ # rubocop: disable CodeReuse/ActiveRecord
+ explanation do |users|
+ users = quick_action_target.allows_multiple_assignees? ? users : users.take(1)
+ "Assigns #{users.map(&:to_reference).to_sentence}."
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ params do
+ quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '@user'
+ end
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ parse_params do |assignee_param|
+ extract_users(assignee_param)
+ end
+ command :assign do |users|
+ next if users.empty?
+
+ if quick_action_target.allows_multiple_assignees?
+ @updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
+ @updates[:assignee_ids] += users.map(&:id)
+ else
+ @updates[:assignee_ids] = [users.first.id]
+ end
+ end
+
+ desc do
+ if quick_action_target.allows_multiple_assignees?
+ 'Remove all or specific assignee(s)'
+ else
+ 'Remove assignee'
+ end
+ end
+ explanation do |users = nil|
+ assignees = quick_action_target.assignees
+ assignees &= users if users.present? && quick_action_target.allows_multiple_assignees?
+ "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
+ end
+ params do
+ quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : ''
+ end
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.assignees.any? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ parse_params do |unassign_param|
+ # When multiple users are assigned, all will be unassigned if multiple assignees are no longer allowed
+ extract_users(unassign_param) if quick_action_target.allows_multiple_assignees?
+ end
+ command :unassign do |users = nil|
+ if quick_action_target.allows_multiple_assignees? && users&.any?
+ @updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
+ @updates[:assignee_ids] -= users.map(&:id)
+ else
+ @updates[:assignee_ids] = []
+ end
+ end
+
+ desc 'Set milestone'
+ explanation do |milestone|
+ "Sets the milestone to #{milestone.to_reference}." if milestone
+ end
+ params '%"milestone"'
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project) &&
+ find_milestones(project, state: 'active').any?
+ end
+ parse_params do |milestone_param|
+ extract_references(milestone_param, :milestone).first ||
+ find_milestones(project, title: milestone_param.strip).first
+ end
+ command :milestone do |milestone|
+ @updates[:milestone_id] = milestone.id if milestone
+ end
+
+ desc 'Remove milestone'
+ explanation do
+ "Removes #{quick_action_target.milestone.to_reference(format: :name)} milestone."
+ end
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.milestone_id? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ command :remove_milestone do
+ @updates[:milestone_id] = nil
+ end
+
+ desc 'Copy labels and milestone from other issue or merge request'
+ explanation do |source_issuable|
+ "Copy labels and milestone from #{source_issuable.to_reference}."
+ end
+ params '#issue | !merge_request'
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ parse_params do |issuable_param|
+ extract_references(issuable_param, :issue).first ||
+ extract_references(issuable_param, :merge_request).first
+ end
+ command :copy_metadata do |source_issuable|
+ if source_issuable.present? && source_issuable.project.id == quick_action_target.project.id
+ @updates[:add_label_ids] = source_issuable.labels.map(&:id)
+ @updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
+ end
+ end
+
+ desc 'Set time estimate'
+ explanation do |time_estimate|
+ time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
+
+ "Sets time estimate to #{time_estimate}." if time_estimate
+ end
+ params '<1w 3d 2h 14m>'
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ parse_params do |raw_duration|
+ Gitlab::TimeTrackingFormatter.parse(raw_duration)
+ end
+ command :estimate do |time_estimate|
+ if time_estimate
+ @updates[:time_estimate] = time_estimate
+ end
+ end
+
+ desc 'Add or subtract spent time'
+ explanation do |time_spent, time_spent_date|
+ if time_spent
+ if time_spent > 0
+ verb = 'Adds'
+ value = time_spent
+ else
+ verb = 'Subtracts'
+ value = -time_spent
+ end
+
+ "#{verb} #{Gitlab::TimeTrackingFormatter.output(value)} spent time."
+ end
+ end
+ params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
+ types Issue, MergeRequest
+ condition do
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ parse_params do |raw_time_date|
+ Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
+ end
+ command :spend do |time_spent, time_spent_date|
+ if time_spent
+ @updates[:spend_time] = {
+ duration: time_spent,
+ user_id: current_user.id,
+ spent_at: time_spent_date
+ }
+ end
+ end
+
+ desc 'Remove time estimate'
+ explanation 'Removes time estimate.'
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ command :remove_estimate do
+ @updates[:time_estimate] = 0
+ end
+
+ desc 'Remove spent time'
+ explanation 'Removes spent time.'
+ condition do
+ quick_action_target.persisted? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
+ end
+ types Issue, MergeRequest
+ command :remove_time_spent do
+ @updates[:spend_time] = { duration: :reset, user_id: current_user.id }
+ end
+
+ desc "Lock the discussion"
+ explanation "Locks the discussion"
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ !quick_action_target.discussion_locked? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :lock do
+ @updates[:discussion_locked] = true
+ end
+
+ desc "Unlock the discussion"
+ explanation "Unlocks the discussion"
+ types Issue, MergeRequest
+ condition do
+ quick_action_target.persisted? &&
+ quick_action_target.discussion_locked? &&
+ current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
+ end
+ command :unlock do
+ @updates[:discussion_locked] = false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb
new file mode 100644
index 00000000000..bade59182a1
--- /dev/null
+++ b/lib/gitlab/quick_actions/merge_request_actions.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module QuickActions
+ module MergeRequestActions
+ extend ActiveSupport::Concern
+ include Gitlab::QuickActions::Dsl
+
+ included do
+ # MergeRequest only quick actions definitions
+ desc 'Merge (when the pipeline succeeds)'
+ explanation 'Merges this merge request when the pipeline succeeds.'
+ types MergeRequest
+ condition do
+ last_diff_sha = params && params[:merge_request_diff_head_sha]
+ quick_action_target.persisted? &&
+ quick_action_target.mergeable_with_quick_action?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
+ end
+ command :merge do
+ @updates[:merge] = params[:merge_request_diff_head_sha]
+ end
+
+ desc 'Toggle the Work In Progress status'
+ explanation do
+ verb = quick_action_target.work_in_progress? ? 'Unmarks' : 'Marks'
+ noun = quick_action_target.to_ability_name.humanize(capitalize: false)
+ "#{verb} this #{noun} as Work In Progress."
+ end
+ types MergeRequest
+ condition do
+ quick_action_target.respond_to?(:work_in_progress?) &&
+ # Allow it to mark as WIP on MR creation page _or_ through MR notes.
+ (quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target))
+ end
+ command :wip do
+ @updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip'
+ end
+
+ desc 'Set target branch'
+ explanation do |branch_name|
+ "Sets target branch to #{branch_name}."
+ end
+ params '<Local branch name>'
+ types MergeRequest
+ condition do
+ quick_action_target.respond_to?(:target_branch) &&
+ (current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) ||
+ quick_action_target.new_record?)
+ end
+ parse_params do |target_branch_param|
+ target_branch_param.strip
+ end
+ command :target_branch do |branch_name|
+ @updates[:target_branch] = branch_name if project.repository.branch_exists?(branch_name)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb
index 202d310e237..207a80b7db2 100644
--- a/lib/gitlab/repo_path.rb
+++ b/lib/gitlab/repo_path.rb
@@ -5,19 +5,26 @@ module Gitlab
NotFoundError = Class.new(StandardError)
def self.parse(repo_path)
- wiki = false
project_path = repo_path.sub(/\.git\z/, '').sub(%r{\A/}, '')
- project, was_redirected = find_project(project_path)
-
- if project_path.end_with?('.wiki') && project.nil?
- project, was_redirected = find_project(project_path.chomp('.wiki'))
- wiki = true
+ # Detect the repo type based on the path, the first one tried is the project
+ # type, which does not have a suffix.
+ Gitlab::GlRepository.types.each do |_name, type|
+ # If the project path does not end with the defined suffix, try the next
+ # type.
+ # We'll always try to find a project with an empty suffix (for the
+ # `Gitlab::GlRepository::PROJECT` type.
+ next unless project_path.end_with?(type.path_suffix)
+
+ project, was_redirected = find_project(project_path.chomp(type.path_suffix))
+ redirected_path = project_path if was_redirected
+
+ # If we found a matching project, then the type was matched, no need to
+ # continue looking.
+ return [project, type, redirected_path] if project
end
- redirected_path = project_path if was_redirected
-
- [project, wiki, redirected_path]
+ nil
end
def self.find_project(project_path)
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 265f6213a99..5d5a867c9ab 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -20,14 +20,14 @@ module Gitlab
SECRET_LENGTH = 32
class << self
- def git_http_ok(repository, is_wiki, user, action, show_all_refs: false)
+ def git_http_ok(repository, repo_type, user, action, show_all_refs: false)
raise "Unsupported action: #{action}" unless ALLOWED_GIT_HTTP_ACTIONS.include?(action.to_s)
project = repository.project
attrs = {
GL_ID: Gitlab::GlId.gl_id(user),
- GL_REPOSITORY: Gitlab::GlRepository.gl_repository(project, is_wiki),
+ GL_REPOSITORY: repo_type.identifier_for_subject(project),
GL_USERNAME: user&.username,
ShowAllRefs: show_all_refs,
Repository: repository.gitaly_repository.to_h,
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 5e0c9101de5..bb1aa2a7a10 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -3,7 +3,7 @@
module Sentry
class Client
Error = Class.new(StandardError)
- SentryError = Class.new(StandardError)
+ MissingKeysError = Class.new(StandardError)
attr_accessor :url, :token
@@ -14,18 +14,29 @@ module Sentry
def list_issues(issue_status:, limit:)
issues = get_issues(issue_status: issue_status, limit: limit)
- map_to_errors(issues)
+
+ handle_mapping_exceptions do
+ map_to_errors(issues)
+ end
end
def list_projects
projects = get_projects
- map_to_projects(projects)
- rescue KeyError => e
- raise Client::SentryError, "Sentry API response is missing keys. #{e.message}"
+
+ handle_mapping_exceptions do
+ map_to_projects(projects)
+ end
end
private
+ def handle_mapping_exceptions(&block)
+ yield
+ rescue KeyError => e
+ Gitlab::Sentry.track_acceptable_exception(e)
+ raise Client::MissingKeysError, "Sentry API response is missing keys. #{e.message}"
+ end
+
def request_params
{
headers: {
@@ -94,7 +105,6 @@ module Sentry
def map_to_error(issue)
id = issue.fetch('id')
- project = issue.fetch('project')
count = issue.fetch('count', nil)
@@ -117,9 +127,9 @@ module Sentry
short_id: issue.fetch('shortId', nil),
status: issue.fetch('status', nil),
frequency: frequency,
- project_id: project.fetch('id'),
- project_name: project.fetch('name', nil),
- project_slug: project.fetch('slug', nil)
+ project_id: issue.dig('project', 'id'),
+ project_name: issue.dig('project', 'name'),
+ project_slug: issue.dig('project', 'slug')
)
end
@@ -127,12 +137,12 @@ module Sentry
organization = project.fetch('organization')
Gitlab::ErrorTracking::Project.new(
- id: project.fetch('id'),
+ id: project.fetch('id', nil),
name: project.fetch('name'),
slug: project.fetch('slug'),
status: project.dig('status'),
organization_name: organization.fetch('name'),
- organization_id: organization.fetch('id'),
+ organization_id: organization.fetch('id', nil),
organization_slug: organization.fetch('slug')
)
end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index b94b21775ee..4e7a8adbef6 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -29,7 +29,10 @@ namespace :gitlab do
# If MySQL, turn off foreign key checks
connection.execute('SET FOREIGN_KEY_CHECKS=0') if Gitlab::Database.mysql?
- tables = connection.data_sources
+ # 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
+
# Removes the entry from the array
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake
index eec024f9bbb..46635cd7c8f 100644
--- a/lib/tasks/tokens.rake
+++ b/lib/tasks/tokens.rake
@@ -24,7 +24,7 @@ namespace :tokens do
end
end
-class TmpUser < ActiveRecord::Base
+class TmpUser < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord
include TokenAuthenticatable
self.table_name = 'users'
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 177bd189817..1e6f04c7815 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -144,6 +144,15 @@ msgstr ""
msgid "%{percent}%% complete"
msgstr ""
+msgid "%{service_title} activated."
+msgstr ""
+
+msgid "%{service_title} settings saved, but not activated."
+msgstr ""
+
+msgid "%{spammable_titlecase} was submitted to Akismet successfully."
+msgstr ""
+
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
msgstr[0] ""
@@ -372,6 +381,9 @@ msgstr ""
msgid "Access expiration date"
msgstr ""
+msgid "Access forbidden. Check your access level."
+msgstr ""
+
msgid "Account"
msgstr ""
@@ -609,9 +621,18 @@ msgstr ""
msgid "All issues for this milestone are closed. You may close this milestone now."
msgstr ""
+msgid "All merge conflicts were resolved. The merge request can now be merged."
+msgstr ""
+
+msgid "All todos were marked as done."
+msgstr ""
+
msgid "All users"
msgstr ""
+msgid "All users must have a name."
+msgstr ""
+
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
@@ -954,6 +975,9 @@ msgstr ""
msgid "Authentication method"
msgstr ""
+msgid "Authentication via U2F device failed."
+msgstr ""
+
msgid "Author"
msgstr ""
@@ -1815,7 +1839,7 @@ msgstr ""
msgid "ClusterIntegration|GitLab Runner"
msgstr ""
-msgid "ClusterIntegration|GitLab Runner connects to this project's repository and executes CI/CD jobs, pushing results back and deploying, applications to production."
+msgid "ClusterIntegration|GitLab Runner connects to the repository and executes CI/CD jobs, pushing results back and deploying applications to production."
msgstr ""
msgid "ClusterIntegration|Google Cloud Platform project"
@@ -2447,6 +2471,15 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not connect to FogBugz, check your URL"
+msgstr ""
+
+msgid "Could not create Wiki Repository at this time. Please try again later."
+msgstr ""
+
+msgid "Could not remove the trigger."
+msgstr ""
+
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
@@ -3370,6 +3403,9 @@ msgstr ""
msgid "Error updating todo status."
msgstr ""
+msgid "Error uploading file"
+msgstr ""
+
msgid "Error while loading the merge request. Please try again."
msgstr ""
@@ -3541,6 +3577,9 @@ msgstr ""
msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
msgstr ""
+msgid "Failed to promote label due to internal error. Please contact administrators."
+msgstr ""
+
msgid "Failed to remove issue from board, please try again."
msgstr ""
@@ -3786,6 +3825,9 @@ msgstr ""
msgid "Git"
msgstr ""
+msgid "Git LFS is not enabled on this GitLab server, contact your admin."
+msgstr ""
+
msgid "Git global setup"
msgstr ""
@@ -3966,6 +4008,9 @@ msgstr ""
msgid "GroupSettings|Learn more about badges."
msgstr ""
+msgid "GroupSettings|New runners registration token has been generated!"
+msgstr ""
+
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
@@ -4130,6 +4175,9 @@ msgstr ""
msgid "Hook was successfully created."
msgstr ""
+msgid "Hook was successfully updated."
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
@@ -4409,12 +4457,21 @@ msgstr ""
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid Login or password"
+msgstr ""
+
+msgid "Invalid file."
+msgstr ""
+
msgid "Invalid input, please avoid emojis"
msgstr ""
msgid "Invalid pin code"
msgstr ""
+msgid "Invalid two-factor code."
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -4478,6 +4535,9 @@ msgstr ""
msgid "Job has been erased"
msgstr ""
+msgid "Job has been successfully erased!"
+msgstr ""
+
msgid "Job is stuck. Check runners."
msgstr ""
@@ -4986,6 +5046,9 @@ msgstr ""
msgid "Merged"
msgstr ""
+msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
+msgstr ""
+
msgid "Messages"
msgstr ""
@@ -5025,6 +5088,9 @@ msgstr ""
msgid "Metrics|There was an error getting environments information."
msgstr ""
+msgid "Metrics|There was an error while retrieving metrics"
+msgstr ""
+
msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
msgstr ""
@@ -5079,6 +5145,9 @@ msgstr ""
msgid "Mirroring repositories"
msgstr ""
+msgid "Mirroring settings were successfully updated."
+msgstr ""
+
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
@@ -5384,6 +5453,9 @@ msgstr ""
msgid "Not enough data"
msgstr ""
+msgid "Not found."
+msgstr ""
+
msgid "Not now"
msgstr ""
@@ -5489,6 +5561,9 @@ msgstr ""
msgid "November"
msgstr ""
+msgid "Object does not exist on the server or you don't have permissions to access it"
+msgstr ""
+
msgid "Oct"
msgstr ""
@@ -5590,6 +5665,12 @@ msgstr ""
msgid "Owner"
msgstr ""
+msgid "Page not found"
+msgstr ""
+
+msgid "Page was successfully deleted"
+msgstr ""
+
msgid "Pages"
msgstr ""
@@ -5752,6 +5833,12 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines page"
+msgstr ""
+
+msgid "Pipelines settings for '%{project_name}' were successfully updated."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr ""
@@ -5896,6 +5983,9 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr ""
+msgid "Please select a group."
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr ""
@@ -6217,6 +6307,12 @@ msgstr ""
msgid "Project \"%{name}\" is no longer available. Select another project to continue."
msgstr ""
+msgid "Project %{project_repo} could not be found"
+msgstr ""
+
+msgid "Project '%{project_name}' is being imported."
+msgstr ""
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr ""
@@ -6696,6 +6792,12 @@ msgstr ""
msgid "Resolved"
msgstr ""
+msgid "Resolved 1 discussion."
+msgstr ""
+
+msgid "Resolved all discussions."
+msgstr ""
+
msgid "Response metrics (AWS ELB)"
msgstr ""
@@ -7019,6 +7121,9 @@ msgstr ""
msgid "September"
msgstr ""
+msgid "Server supports batch API only, please update your Git LFS client to version 1.0.1 and up."
+msgstr ""
+
msgid "Server version"
msgstr ""
@@ -7603,6 +7708,9 @@ msgstr ""
msgid "Successfully removed email."
msgstr ""
+msgid "Successfully scheduled a pipeline to run. Go to the %{link_to_pipelines} for details."
+msgstr ""
+
msgid "Successfully unblocked"
msgstr ""
@@ -7756,6 +7864,9 @@ msgstr ""
msgid "Test coverage parsing"
msgstr ""
+msgid "Test failed."
+msgstr ""
+
msgid "The Git LFS objects will <strong>not</strong> be synced."
msgstr ""
@@ -7774,9 +7885,24 @@ msgstr ""
msgid "The collection of events added to the data gathered for that stage."
msgstr ""
+msgid "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
+msgstr ""
+
msgid "The deployment of this job to %{environmentLink} did not succeed."
msgstr ""
+msgid "The directory has been successfully created."
+msgstr ""
+
+msgid "The entered user map is not a valid JSON user map."
+msgstr ""
+
+msgid "The file has been successfully created."
+msgstr ""
+
+msgid "The file has been successfully deleted."
+msgstr ""
+
msgid "The fork relationship has been removed."
msgstr ""
@@ -7789,6 +7915,12 @@ msgstr ""
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
+msgid "The invitation has already been accepted."
+msgstr ""
+
+msgid "The invitation was successfully resent."
+msgstr ""
+
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
msgstr ""
@@ -7798,6 +7930,15 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
+msgid "The merge conflicts for this merge request cannot be resolved through GitLab. Please try to resolve them locally."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved."
+msgstr ""
+
+msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
+msgstr ""
+
msgid "The name %{entryName} is already taken in this directory."
msgstr ""
@@ -7822,6 +7963,15 @@ msgstr ""
msgid "The project can be accessed without any authentication."
msgstr ""
+msgid "The project was successfully forked."
+msgstr ""
+
+msgid "The project was successfully imported."
+msgstr ""
+
+msgid "The remote repository is being updated..."
+msgstr ""
+
msgid "The repository for this project does not exist."
msgstr ""
@@ -7852,12 +8002,18 @@ msgstr ""
msgid "The update action will time out after %{number_of_minutes} minutes. For big repositories, use a clone/push combination."
msgstr ""
+msgid "The uploaded file is not a valid Google Takeout archive."
+msgstr ""
+
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
msgid "The user is being deleted."
msgstr ""
+msgid "The user map has been saved. Continue by selecting the projects you want to import."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -7921,6 +8077,9 @@ msgstr ""
msgid "There was an error when unsubscribing from this label."
msgstr ""
+msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
+msgstr ""
+
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
msgstr ""
@@ -8071,6 +8230,9 @@ msgstr ""
msgid "This merge request is locked."
msgstr ""
+msgid "This namespace has already been taken! Please choose another one."
+msgstr ""
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr ""
@@ -8107,6 +8269,9 @@ msgstr ""
msgid "This repository"
msgstr ""
+msgid "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch."
+msgstr ""
+
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
@@ -8393,6 +8558,9 @@ msgstr ""
msgid "Todo"
msgstr ""
+msgid "Todo was successfully marked as done."
+msgstr ""
+
msgid "Todos"
msgstr ""
@@ -8450,6 +8618,9 @@ msgstr ""
msgid "Trending"
msgstr ""
+msgid "Trigger removed."
+msgstr ""
+
msgid "Trigger this manual action"
msgstr ""
@@ -8459,6 +8630,15 @@ msgstr ""
msgid "Trigger variables:"
msgstr ""
+msgid "Trigger was created successfully."
+msgstr ""
+
+msgid "Trigger was re-assigned."
+msgstr ""
+
+msgid "Trigger was successfully updated."
+msgstr ""
+
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
msgstr ""
@@ -8483,9 +8663,15 @@ msgstr ""
msgid "Type"
msgstr ""
+msgid "Unable to connect to server: %{error}"
+msgstr ""
+
msgid "Unable to load the diff. %{button_try_again}"
msgstr ""
+msgid "Unable to schedule a pipeline to run immediately"
+msgstr ""
+
msgid "Unblock"
msgstr ""
@@ -8561,6 +8747,9 @@ msgstr ""
msgid "Update now"
msgstr ""
+msgid "Update your bookmarked URLs as filtered/sorted branches URL has been changed."
+msgstr ""
+
msgid "Update your group name, description, avatar, and visibility."
msgstr ""
@@ -8657,6 +8846,12 @@ msgstr ""
msgid "User was successfully created."
msgstr ""
+msgid "User was successfully removed from group and any subresources."
+msgstr ""
+
+msgid "User was successfully removed from project."
+msgstr ""
+
msgid "User was successfully updated."
msgstr ""
@@ -8750,6 +8945,9 @@ msgstr ""
msgid "Validate your GitLab CI configuration file"
msgstr ""
+msgid "Validations failed."
+msgstr ""
+
msgid "Value"
msgstr ""
@@ -8888,6 +9086,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki was successfully updated."
+msgstr ""
+
msgid "WikiClone|Clone your wiki"
msgstr ""
@@ -9107,6 +9308,12 @@ msgstr ""
msgid "You can move around the graph by using the arrow keys."
msgstr ""
+msgid "You can now submit a merge request to get this change into the original branch."
+msgstr ""
+
+msgid "You can now submit a merge request to get this change into the original project."
+msgstr ""
+
msgid "You can only add files when you are on a branch"
msgstr ""
@@ -9134,9 +9341,18 @@ msgstr ""
msgid "You cannot impersonate an internal user"
msgstr ""
+msgid "You cannot play this scheduled pipeline at the moment. Please wait a minute."
+msgstr ""
+
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
+msgid "You could not create a new trigger."
+msgstr ""
+
+msgid "You could not take ownership of trigger."
+msgstr ""
+
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -9155,6 +9371,9 @@ msgstr ""
msgid "You have reached your project limit"
msgstr ""
+msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
+msgstr ""
+
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
msgstr ""
@@ -9170,6 +9389,15 @@ msgstr ""
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
msgstr ""
+msgid "You need to specify both an Access Token and a Host URL."
+msgstr ""
+
+msgid "You need to upload a GitLab project export archive (ending in .gz)."
+msgstr ""
+
+msgid "You need to upload a Google Takeout archive."
+msgstr ""
+
msgid "You will lose all changes you've made to this file. This action cannot be undone."
msgstr ""
@@ -9242,6 +9470,9 @@ msgstr ""
msgid "Your U2F device was registered!"
msgstr ""
+msgid "Your access request to the %{source_type} has been withdrawn."
+msgstr ""
+
msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
msgstr ""
@@ -9263,6 +9494,9 @@ msgstr ""
msgid "Your changes have been saved"
msgstr ""
+msgid "Your changes have been successfully committed."
+msgstr ""
+
msgid "Your comment will not be visible to the public."
msgstr ""
@@ -9287,6 +9521,9 @@ msgstr ""
msgid "Your projects"
msgstr ""
+msgid "Your request for access has been queued for review."
+msgstr ""
+
msgid "a deleted user"
msgstr ""
@@ -9366,6 +9603,9 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
+msgid "failed"
+msgstr ""
+
msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
msgstr ""
@@ -9743,6 +9983,9 @@ msgstr ""
msgid "stuck"
msgstr ""
+msgid "success"
+msgstr ""
+
msgid "syntax is correct"
msgstr ""
diff --git a/package.json b/package.json
index 1154d57cad2..f80f9b3690b 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,8 @@
"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/**/*.* --custom-formatter node_modules/stylelint-error-string-formatter",
+ "stylelint-file": "node node_modules/stylelint/bin/stylelint.js",
+ "stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js",
"test": "yarn jest && yarn karma",
"webpack": "webpack --config config/webpack.config.js",
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
@@ -29,8 +31,8 @@
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.3.1",
- "@gitlab/csslab": "^1.8.0",
- "@gitlab/svgs": "^1.54.0",
+ "@gitlab/csslab": "^1.9.0",
+ "@gitlab/svgs": "^1.57.0",
"@gitlab/ui": "^3.0.0",
"apollo-boost": "^0.3.1",
"apollo-client": "^2.5.1",
@@ -38,7 +40,7 @@
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.5",
- "bootstrap": "4.1.3",
+ "bootstrap": "4.3.1",
"brace-expansion": "^1.1.8",
"cache-loader": "^2.0.1",
"chart.js": "2.7.2",
@@ -89,7 +91,7 @@
"monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
"pikaday": "^1.6.1",
- "popper.js": "^1.14.3",
+ "popper.js": "^1.14.7",
"prismjs": "^1.6.0",
"prosemirror-markdown": "^1.3.0",
"prosemirror-model": "^1.6.4",
@@ -116,14 +118,14 @@
"underscore": "^1.9.0",
"url-loader": "^1.1.2",
"visibilityjs": "^1.2.4",
- "vue": "^2.5.21",
+ "vue": "^2.6.10",
"vue-apollo": "^3.0.0-beta.28",
- "vue-loader": "^15.4.2",
+ "vue-loader": "^15.7.0",
"vue-resource": "^1.5.1",
"vue-router": "^3.0.2",
- "vue-template-compiler": "^2.5.21",
- "vue-virtual-scroll-list": "^1.2.5",
- "vuex": "^3.0.1",
+ "vue-template-compiler": "^2.6.10",
+ "vue-virtual-scroll-list": "^1.3.1",
+ "vuex": "^3.1.0",
"webpack": "^4.29.0",
"webpack-bundle-analyzer": "^3.0.3",
"webpack-cli": "^3.2.1",
@@ -142,6 +144,7 @@
"babel-plugin-rewire": "^1.2.0",
"chalk": "^2.4.1",
"commander": "^2.18.0",
+ "custom-jquery-matchers": "^2.1.0",
"docdash": "^1.0.2",
"eslint": "~5.9.0",
"eslint-import-resolver-jest": "^2.1.1",
@@ -150,15 +153,17 @@
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jasmine": "^2.10.1",
"eslint-plugin-jest": "^22.3.0",
- "gettext-extractor": "^3.3.2",
- "gettext-extractor-vue": "^4.0.1",
+ "gettext-extractor": "^3.4.3",
+ "gettext-extractor-vue": "^4.0.2",
"graphql-tag": "^2.10.0",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
"jasmine-jquery": "^2.1.1",
"jest": "^24.1.0",
+ "jest-environment-jsdom": "^24.0.0",
"jest-junit": "^6.3.0",
+ "jest-util": "^24.0.0",
"jsdoc": "^3.5.5",
"jsdoc-vue": "^1.0.0",
"karma": "^3.0.0",
@@ -169,13 +174,15 @@
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-beta.0",
+ "md5": "^2.2.1",
+ "node-sass": "^4.11.0",
"nodemon": "^1.18.9",
"pixelmatch": "^4.0.2",
"postcss": "^7.0.14",
"prettier": "1.16.4",
"stylelint": "^9.10.1",
"stylelint-config-recommended": "^2.1.0",
- "stylelint-scss": "^3.5.3",
+ "stylelint-scss": "^3.5.4",
"vue-jest": "^4.0.0-beta.2",
"webpack-dev-server": "^3.1.14",
"yarn-deduplicate": "^1.1.1"
diff --git a/qa/README.md b/qa/README.md
index 735868e7640..7d66f7d5abc 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -55,16 +55,19 @@ You can also supply specific tests to run as another parameter. For example, to
run the repository-related specs, you can execute:
```
-bin/qa Test::Instance::All http://localhost qa/specs/features/repository/
+bin/qa Test::Instance::All http://localhost -- 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:
```
-bin/qa Test::Instance::All http://localhost qa/specs/features/project/create_spec.rb:3 --backtrace
+bin/qa Test::Instance::All http://localhost -- 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
+ignored by the QA framework and passed to `rspec`.
+
### Overriding the authenticated user
Unless told otherwise, the QA tests will run as the default `root` user seeded
@@ -117,7 +120,7 @@ tests that are expected to fail while a fix is in progress (similar to how
can be used).
```
-bin/qa Test::Instance::All http://localhost --tag quarantine
+bin/qa Test::Instance::All http://localhost -- --tag quarantine
```
If `quarantine` is used with other tags, tests will only be run if they have at
@@ -128,3 +131,25 @@ For example, suppose one test has `:smoke` and `:quarantine` metadata, and
another test has `:ldap` and `:quarantine` metadata. If the tests are run with
`--tag smoke --tag quarantine`, only the first test will run. The test with
`:ldap` will not run even though it also has `:quarantine`.
+
+### Running tests with a feature flag enabled
+
+Tests can be run with with a feature flag enabled by using the command-line
+option `--enable-feature FEATURE_FLAG`. For example, to enable the feature flag
+that enforces Gitaly request limits, you would use the command:
+
+```
+bin/qa Test::Instance::All http://localhost --enable-feature gitaly_enforce_requests_limits
+```
+
+This will instruct the QA framework to enable the `gitaly_enforce_requests_limits`
+feature flag ([via the API](https://docs.gitlab.com/ee/api/features.html)), run
+all the tests in the `Test::Instance::All` scenario, and then disable the
+feature flag again.
+
+Note: the QA framework doesn't currently allow you to easily toggle a feature
+flag during a single test, [as you can in unit tests](https://docs.gitlab.com/ee/development/feature_flags.html#specs),
+but [that capability is planned](https://gitlab.com/gitlab-org/quality/team-tasks/issues/77).
+
+Note also that the `--` separator isn't used because `--enable-feature` is a QA
+framework option, not an `rspec` option. \ No newline at end of file
diff --git a/qa/Rakefile b/qa/Rakefile
index d0101740f1a..7ac018f7286 100644
--- a/qa/Rakefile
+++ b/qa/Rakefile
@@ -27,9 +27,9 @@ task :run_artillery_load_tests do
end
urls = YAML.safe_load(File.read(urls_file))
- ENV['HOST_URL'] = urls[:host]
- ENV['LARGE_ISSUE_URL'] = urls[:large_issue]
- ENV['LARGE_MR_URL'] = urls[:large_mr]
+ ENV['HOST_URL'] = urls["host"]
+ ENV['LARGE_ISSUE_URL'] = urls["large_issue"]
+ ENV['LARGE_MR_URL'] = urls["large_mr"]
end
sh('artillery run load/artillery.yml -o report.json')
diff --git a/qa/load/artillery.yml b/qa/load/artillery.yml
index e2c3c293d8b..17d253ec480 100644
--- a/qa/load/artillery.yml
+++ b/qa/load/artillery.yml
@@ -1,15 +1,18 @@
config:
target: "{{ $processEnvironment.HOST_URL }}"
+ http:
+ pool: 10 # All HTTP requests from all virtual users will be sent over the same <pool> connections.
+ # This also means that there is a limit on the number of requests sent per second.
phases:
- - duration: 60
- arrivalRate: 1
+ - duration: 30
+ arrivalRate: 10
name: "Warm up"
- - duration: 120
- arrivalRate: 1
- rampTo: 50
+ - duration: 90
+ arrivalRate: 10
+ rampTo: 100
name: "Gradual ramp up"
- - duration: 60
- arrivalRate: 50
+ - duration: 90
+ arrivalRate: 100
name: "Sustained max load"
scenarios:
- name: "Visit large issue url"
diff --git a/qa/qa.rb b/qa/qa.rb
index ec8aef31e48..672f4aa928a 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -17,6 +17,7 @@ module QA
autoload :Env, 'qa/runtime/env'
autoload :Address, 'qa/runtime/address'
autoload :Path, 'qa/runtime/path'
+ autoload :Feature, 'qa/runtime/feature'
autoload :Fixtures, 'qa/runtime/fixtures'
autoload :Logger, 'qa/runtime/logger'
@@ -89,6 +90,7 @@ module QA
autoload :Bootable, 'qa/scenario/bootable'
autoload :Actable, 'qa/scenario/actable'
autoload :Template, 'qa/scenario/template'
+ autoload :SharedAttributes, 'qa/scenario/shared_attributes'
##
# Test scenario entrypoints.
diff --git a/qa/qa/page/dashboard/groups.rb b/qa/qa/page/dashboard/groups.rb
index 7a07515de62..52853376f17 100644
--- a/qa/qa/page/dashboard/groups.rb
+++ b/qa/qa/page/dashboard/groups.rb
@@ -19,11 +19,13 @@ module QA
has_filtered_group?(name)
end
- def go_to_group(name)
+ def click_group(name)
+ raise "Group with name #{name} not found!" unless has_group?(name)
+
click_link name
end
- def go_to_new_group
+ def click_new_group
click_on 'New group'
end
end
diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb
index a637b869d2f..4a8e65e20af 100644
--- a/qa/qa/page/dashboard/snippet/new.rb
+++ b/qa/qa/page/dashboard/snippet/new.rb
@@ -37,7 +37,7 @@ module QA
text_area.set content
end
- def create_snippet
+ def click_create_snippet_button
click_element :create_snippet_button
end
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 41716326685..d4c4be0d6ca 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -18,7 +18,7 @@ module QA
element :no_result_text, 'No groups or projects matched your search' # rubocop:disable QA/ElementWithPattern
end
- def go_to_subgroup(name)
+ def click_subgroup(name)
click_link name
end
diff --git a/qa/qa/page/label/index.rb b/qa/qa/page/label/index.rb
index de0cfa9f293..e73d40b37ac 100644
--- a/qa/qa/page/label/index.rb
+++ b/qa/qa/page/label/index.rb
@@ -18,7 +18,7 @@ module QA
element :label_svg
end
- def go_to_new_label
+ def click_new_label_button
# The 'labels.svg' takes a fraction of a second to load after which the "New label" button shifts up a bit
# This can cause webdriver to miss the hit so we wait for the svg to load (implicitly with has_element?)
# before clicking the button.
diff --git a/qa/qa/page/label/new.rb b/qa/qa/page/label/new.rb
index b5422dc9400..9c8cf5f07e6 100644
--- a/qa/qa/page/label/new.rb
+++ b/qa/qa/page/label/new.rb
@@ -9,7 +9,7 @@ module QA
element :label_create_button
end
- def create_label
+ def click_label_create_button
click_element :label_create_button
end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 1b3445b0064..e98d531c86e 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -47,7 +47,7 @@ module QA
end
end
- def go_to_admin_area
+ def click_admin_area
within_top_menu { click_element :admin_area_link }
end
@@ -57,7 +57,7 @@ module QA
end
end
- def go_to_profile_settings
+ def click_settings_link
retry_until(reload: false) do
within_user_menu do
click_link 'Settings'
@@ -67,7 +67,7 @@ module QA
end
end
- def go_to_snippets
+ def click_snippets_link
click_element :snippets_link
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index c0411db6505..5aef868a805 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -145,11 +145,11 @@ module QA
click_element :squash_checkbox
end
- def go_to_discussions_tab
+ def click_discussions_tab
click_element :notes_tab
end
- def go_to_diffs_tab
+ def click_diffs_tab
click_element :diffs_tab
end
diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb
index 8c12eff5cf1..c9a5a1b3ca0 100644
--- a/qa/qa/page/profile/personal_access_tokens.rb
+++ b/qa/qa/page/profile/personal_access_tokens.rb
@@ -26,7 +26,7 @@ module QA
check_element(:api_radio)
end
- def create_token
+ def click_create_token_button
click_element(:create_token_button)
end
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index 1035bf74a43..e987d279e21 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -7,7 +7,7 @@ module QA
element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
end
- def go_to_issue(title)
+ def click_issue_link(title)
click_link(title)
end
end
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 9c218f4ed8b..5853f487f0b 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA::Page
module Project::Job
class Show < QA::Page::Base
@@ -31,7 +33,9 @@ module QA::Page
private
def loaded?(wait: 60)
- has_element?(:build_trace, wait: wait)
+ wait(reload: true, max: wait, interval: 1) do
+ has_element?(:build_trace, wait: 1)
+ end
end
def completed?(timeout: 60)
diff --git a/qa/qa/page/project/milestone/new.rb b/qa/qa/page/project/milestone/new.rb
index 992ef89004b..0db8d2cf766 100644
--- a/qa/qa/page/project/milestone/new.rb
+++ b/qa/qa/page/project/milestone/new.rb
@@ -17,7 +17,7 @@ module QA
fill_element :milestone_description, description
end
- def create_new_milestone
+ def click_milestone_create_button
click_element :milestone_create_button
end
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 552b2293115..f841afbbffd 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -35,7 +35,7 @@ module QA
end
end
- def go_to_import_project
+ def click_import_project
click_on 'Import project'
end
@@ -51,7 +51,7 @@ module QA
click_on 'Create project'
end
- def go_to_create_from_template
+ def click_create_from_template_tab
click_element(:project_create_from_template_tab)
end
@@ -59,7 +59,7 @@ module QA
choose visibility
end
- def go_to_github_import
+ def click_github_link
click_link 'GitHub'
end
end
diff --git a/qa/qa/page/project/operations/environments/index.rb b/qa/qa/page/project/operations/environments/index.rb
index 63965a57edd..610a34385b1 100644
--- a/qa/qa/page/project/operations/environments/index.rb
+++ b/qa/qa/page/project/operations/environments/index.rb
@@ -10,7 +10,7 @@ module QA
element :environment_link
end
- def go_to_environment(environment_name)
+ def click_environment_link(environment_name)
wait(reload: false) do
find(element_selector_css(:environment_link), text: environment_name).click
end
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index 19d83ecc4f4..0373a12f597 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -5,7 +5,7 @@ module QA::Page
element :pipeline_link, 'class="js-pipeline-url-link"' # rubocop:disable QA/ElementWithPattern
end
- def go_to_latest_pipeline
+ def click_on_latest_pipeline
css = '.js-pipeline-url-link'
link = wait(reload: false) do
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index 6f8a66bf527..9c0b55b1c4c 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -42,11 +42,11 @@ module QA::Page
end
end
- def go_to_job(job_name)
+ def click_job(job_name)
find_element(:job_link, text: job_name).click
end
- def go_to_first_job
+ def click_on_first_job
css = '.js-pipeline-graph-job-link'
wait(reload: false) do
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index 76591a4e3fe..399a49d2420 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -32,22 +32,22 @@ module QA
end
def allow_no_one_to_push
- click_allow(:push, 'No one')
+ go_to_allow(:push, 'No one')
end
def allow_devs_and_maintainers_to_push
- click_allow(:push, 'Developers + Maintainers')
+ go_to_allow(:push, 'Developers + Maintainers')
end
# @deprecated
alias_method :allow_devs_and_masters_to_push, :allow_devs_and_maintainers_to_push
def allow_no_one_to_merge
- click_allow(:merge, 'No one')
+ go_to_allow(:merge, 'No one')
end
def allow_devs_and_maintainers_to_merge
- click_allow(:merge, 'Developers + Maintainers')
+ go_to_allow(:merge, 'Developers + Maintainers')
end
# @deprecated
@@ -59,7 +59,7 @@ module QA
private
- def click_allow(action, text)
+ def go_to_allow(action, text)
click_element :"allowed_to_#{action}_select"
within_element(:"allowed_to_#{action}_dropdown") do
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 9c21d9ddbfa..1a9a2fd413f 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -76,13 +76,13 @@ module QA
click_on 'Fork'
end
- def go_to_file(filename)
+ def click_file(filename)
within_element(:file_tree) do
click_on filename
end
end
- def go_to_commit(commit_msg)
+ def click_commit(commit_msg)
within_element(:file_tree) do
click_on commit_msg
end
diff --git a/qa/qa/page/project/sub_menus/operations.rb b/qa/qa/page/project/sub_menus/operations.rb
index cf9fc453565..24a99a9464c 100644
--- a/qa/qa/page/project/sub_menus/operations.rb
+++ b/qa/qa/page/project/sub_menus/operations.rb
@@ -14,7 +14,7 @@ module QA
end
end
- def click_operations_environments
+ def go_to_operations_environments
hover_operations do
within_submenu do
click_element(:operations_environments_link)
@@ -22,7 +22,7 @@ module QA
end
end
- def click_operations_kubernetes
+ def go_to_operations_kubernetes
hover_operations do
within_submenu do
click_link('Kubernetes')
diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb
index 29eaa9a74de..4cc73a6b25a 100644
--- a/qa/qa/page/project/sub_menus/repository.rb
+++ b/qa/qa/page/project/sub_menus/repository.rb
@@ -20,7 +20,7 @@ module QA
end
end
- def click_repository_branches
+ def go_to_repository_branches
hover_repository do
within_submenu do
click_element(:branches_link)
diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb
index 62c594c0210..22743ebd0a1 100644
--- a/qa/qa/page/project/sub_menus/settings.rb
+++ b/qa/qa/page/project/sub_menus/settings.rb
@@ -14,7 +14,7 @@ module QA
end
end
- def click_ci_cd_settings
+ def go_to_ci_cd_settings
hover_settings do
within_submenu do
click_link('CI / CD')
@@ -22,7 +22,7 @@ module QA
end
end
- def click_members_settings
+ def go_to_members_settings
hover_settings do
within_submenu do
click_element :link_members_settings
@@ -30,7 +30,7 @@ module QA
end
end
- def click_repository_settings
+ def go_to_repository_settings
hover_settings do
within_submenu do
click_link('Repository')
@@ -38,7 +38,7 @@ module QA
end
end
- def go_to_settings
+ def click_settings
within_sidebar do
click_on 'Settings'
end
diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb
index 8d0eafa1818..1ccd67349c3 100644
--- a/qa/qa/page/project/wiki/edit.rb
+++ b/qa/qa/page/project/wiki/edit.rb
@@ -9,15 +9,7 @@ module QA
element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern
end
- def go_to_new_page
- click_on 'New page'
- end
-
- def got_to_view_history_page
- click_on 'Page history'
- end
-
- def go_to_edit_page
+ def click_edit
click_on 'Edit'
end
end
diff --git a/qa/qa/page/project/wiki/new.rb b/qa/qa/page/project/wiki/new.rb
index b90e03be36a..792eba4bab7 100644
--- a/qa/qa/page/project/wiki/new.rb
+++ b/qa/qa/page/project/wiki/new.rb
@@ -23,7 +23,7 @@ module QA
element :svg_content
end
- def go_to_create_first_page
+ def click_create_your_first_page_button
# The svg takes a fraction of a second to load after which the
# "Create your first page" button shifts up a bit. This can cause
# webdriver to miss the hit so we wait for the svg to load before
diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb
index dffbc5d60a2..f79ad510084 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -11,7 +11,7 @@ module QA
element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern
end
- def go_to_clone_repository
+ def click_clone_repository
click_on 'Clone repository'
end
end
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index 98eebac0880..de04467ff5b 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -8,9 +8,6 @@ module QA
module ApiFabricator
include Capybara::DSL
- HTTP_STATUS_OK = 200
- HTTP_STATUS_CREATED = 201
-
ResourceNotFoundError = Class.new(RuntimeError)
ResourceFabricationFailedError = Class.new(RuntimeError)
ResourceURLMissingError = Class.new(RuntimeError)
diff --git a/qa/qa/resource/branch.rb b/qa/qa/resource/branch.rb
index bd52c4abe02..a45dd030625 100644
--- a/qa/qa/resource/branch.rb
+++ b/qa/qa/resource/branch.rb
@@ -46,7 +46,7 @@ module QA
# to `allow_to_push` variable.
return branch unless @protected
- Page::Project::Menu.perform(&:click_repository_settings)
+ Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_branches do |page|
diff --git a/qa/qa/resource/ci_variable.rb b/qa/qa/resource/ci_variable.rb
index 0570c47d41c..d82de4cb816 100644
--- a/qa/qa/resource/ci_variable.rb
+++ b/qa/qa/resource/ci_variable.rb
@@ -15,7 +15,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_settings)
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
Page::Project::Settings::CICD.perform do |setting|
setting.expand_ci_variables do |page|
diff --git a/qa/qa/resource/deploy_key.rb b/qa/qa/resource/deploy_key.rb
index 9565598efb0..869e2a71e47 100644
--- a/qa/qa/resource/deploy_key.rb
+++ b/qa/qa/resource/deploy_key.rb
@@ -23,7 +23,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.perform(&:click_repository_settings)
+ Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |page|
diff --git a/qa/qa/resource/deploy_token.rb b/qa/qa/resource/deploy_token.rb
index cee4422f6b4..fca5ed83c87 100644
--- a/qa/qa/resource/deploy_token.rb
+++ b/qa/qa/resource/deploy_token.rb
@@ -32,7 +32,7 @@ module QA
project.visit!
Page::Project::Menu.act do
- click_repository_settings
+ go_to_repository_settings
end
Page::Project::Settings::Repository.perform do |setting|
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index d7f9ec6a836..0b567a474c8 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -21,7 +21,7 @@ module QA
Page::Group::Show.perform do |group_show|
if group_show.has_subgroup?(path)
- group_show.go_to_subgroup(path)
+ group_show.click_subgroup(path)
else
group_show.go_to_new_subgroup
diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb
index 93a06be6818..27ab7b60211 100644
--- a/qa/qa/resource/kubernetes_cluster.rb
+++ b/qa/qa/resource/kubernetes_cluster.rb
@@ -16,7 +16,7 @@ module QA
@project.visit!
Page::Project::Menu.perform(
- &:click_operations_kubernetes)
+ &:go_to_operations_kubernetes)
Page::Project::Operations::Kubernetes::Index.perform(
&:add_kubernetes_cluster)
diff --git a/qa/qa/resource/label.rb b/qa/qa/resource/label.rb
index c0869cb1f2a..7c899db31f3 100644
--- a/qa/qa/resource/label.rb
+++ b/qa/qa/resource/label.rb
@@ -25,13 +25,13 @@ module QA
project.visit!
Page::Project::Menu.perform(&:go_to_labels)
- Page::Label::Index.perform(&:go_to_new_label)
+ Page::Label::Index.perform(&:click_new_label_button)
Page::Label::New.perform do |page|
page.fill_title(@title)
page.fill_description(@description)
page.fill_color(@color)
- page.create_label
+ page.click_label_create_button
end
end
end
diff --git a/qa/qa/resource/personal_access_token.rb b/qa/qa/resource/personal_access_token.rb
index b8dd0a3562f..f5c632cd8d2 100644
--- a/qa/qa/resource/personal_access_token.rb
+++ b/qa/qa/resource/personal_access_token.rb
@@ -13,13 +13,13 @@ module QA
end
def fabricate!
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_access_tokens)
Page::Profile::PersonalAccessTokens.perform do |page|
page.fill_token_name(name || 'api-test-token')
page.check_api
- page.create_token
+ page.click_create_token_button
end
end
end
diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb
index 0d25e7dd842..a160cdb3273 100644
--- a/qa/qa/resource/project_imported_from_github.rb
+++ b/qa/qa/resource/project_imported_from_github.rb
@@ -18,11 +18,11 @@ module QA
Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page|
- page.go_to_import_project
+ page.click_import_project
end
Page::Project::New.perform do |page|
- page.go_to_github_import
+ page.click_github_link
end
Page::Project::Import::Github.perform do |page|
diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb
index a4d6657caff..8ace75f695a 100644
--- a/qa/qa/resource/project_milestone.rb
+++ b/qa/qa/resource/project_milestone.rb
@@ -28,7 +28,7 @@ module QA
Page::Project::Milestone::New.perform do |milestone_new|
milestone_new.set_title(@title)
milestone_new.set_description(@description)
- milestone_new.create_new_milestone
+ milestone_new.click_milestone_create_button
end
end
end
diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb
index 77c4c8a514d..95712300854 100644
--- a/qa/qa/resource/repository/wiki_push.rb
+++ b/qa/qa/resource/repository/wiki_push.rb
@@ -24,7 +24,7 @@ module QA
@repository_http_uri ||= begin
wiki.visit!
Page::Project::Wiki::Show.act do
- go_to_clone_repository
+ click_clone_repository
choose_repository_clone_http
repository_location.uri
end
diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb
index 08ae3f22117..3344ad3360a 100644
--- a/qa/qa/resource/runner.rb
+++ b/qa/qa/resource/runner.rb
@@ -29,7 +29,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Menu.perform(&:click_ci_cd_settings)
+ Page::Project::Menu.perform(&:go_to_ci_cd_settings)
Service::Runner.new(name).tap do |runner|
Page::Project::Settings::CICD.perform do |settings|
diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb
index 41ce857a8b8..942eea5cc40 100644
--- a/qa/qa/resource/sandbox.rb
+++ b/qa/qa/resource/sandbox.rb
@@ -20,9 +20,9 @@ module QA
Page::Dashboard::Groups.perform do |page|
if page.has_group?(path)
- page.go_to_group(path)
+ page.click_group(path)
else
- page.go_to_new_group
+ page.click_new_group
Page::Group::New.perform do |group|
group.set_path(path)
diff --git a/qa/qa/resource/settings/hashed_storage.rb b/qa/qa/resource/settings/hashed_storage.rb
index 40c06768ffe..08bb95cfd4b 100644
--- a/qa/qa/resource/settings/hashed_storage.rb
+++ b/qa/qa/resource/settings/hashed_storage.rb
@@ -8,7 +8,7 @@ module QA
raise ArgumentError unless traits.include?(:enabled)
Page::Main::Login.perform(&:sign_in_using_credentials)
- Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Main::Menu.perform(&:click_admin_area)
Page::Admin::Menu.perform(&:go_to_repository_settings)
Page::Admin::Settings::Repository.perform do |setting|
diff --git a/qa/qa/resource/snippet.rb b/qa/qa/resource/snippet.rb
index 1478f197570..f58d7b5113f 100644
--- a/qa/qa/resource/snippet.rb
+++ b/qa/qa/resource/snippet.rb
@@ -22,7 +22,7 @@ module QA
page.set_visibility(@visibility)
page.fill_file_name(@file_name)
page.fill_file_content(@file_content)
- page.create_snippet
+ page.click_create_snippet_button
end
end
end
diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb
index c6c97c8532f..4e399ae730e 100644
--- a/qa/qa/resource/ssh_key.rb
+++ b/qa/qa/resource/ssh_key.rb
@@ -14,7 +14,7 @@ module QA
end
def fabricate!
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |page|
diff --git a/qa/qa/resource/wiki.rb b/qa/qa/resource/wiki.rb
index e942e9718a0..0a776f22e25 100644
--- a/qa/qa/resource/wiki.rb
+++ b/qa/qa/resource/wiki.rb
@@ -18,7 +18,7 @@ module QA
Page::Project::Menu.perform { |menu_side| menu_side.click_wiki }
Page::Project::Wiki::New.perform do |wiki_new|
- wiki_new.go_to_create_first_page
+ wiki_new.click_create_your_first_page_button
wiki_new.set_title(@title)
wiki_new.set_content(@content)
wiki_new.set_message(@message)
diff --git a/qa/qa/runtime/address.rb b/qa/qa/runtime/address.rb
index ffad3974b02..af0537dc17c 100644
--- a/qa/qa/runtime/address.rb
+++ b/qa/qa/runtime/address.rb
@@ -15,6 +15,13 @@ module QA
@instance.to_s
end
end
+
+ def self.valid?(value)
+ uri = URI.parse(value)
+ uri.is_a?(URI::HTTP) && !uri.host.nil?
+ rescue URI::InvalidURIError
+ false
+ end
end
end
end
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index aff84c89f0e..d3327b49339 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -14,7 +14,7 @@ module QA
def personal_access_token
@personal_access_token ||= begin
- # you can set the environment variable PERSONAL_ACCESS_TOKEN
+ # you can set the environment variable GITLAB_QA_ACCESS_TOKEN
# to use a specific access token rather than create one from the UI
Runtime::Env.personal_access_token ||= create_personal_access_token
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index dd0ddbdbd6b..03cae3c1fe6 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -53,7 +53,7 @@ module QA
# specifies token that can be used for the api
def personal_access_token
- @personal_access_token ||= ENV['PERSONAL_ACCESS_TOKEN']
+ @personal_access_token ||= ENV['GITLAB_QA_ACCESS_TOKEN']
end
def remote_grid
diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb
new file mode 100644
index 00000000000..1b4ae7adbbe
--- /dev/null
+++ b/qa/qa/runtime/feature.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module QA
+ module Runtime
+ module Feature
+ extend self
+ extend Support::Api
+
+ SetFeatureError = Class.new(RuntimeError)
+
+ def enable(key)
+ QA::Runtime::Logger.info("Enabling feature: #{key}")
+ set_feature(key, true)
+ end
+
+ def disable(key)
+ QA::Runtime::Logger.info("Disabling feature: #{key}")
+ set_feature(key, false)
+ end
+
+ private
+
+ def api_client
+ @api_client ||= Runtime::API::Client.new(:gitlab)
+ end
+
+ def set_feature(key, value)
+ request = Runtime::API::Request.new(api_client, "/features/#{key}")
+ response = post(request.url, { value: value })
+ unless response.code == QA::Support::Api::HTTP_STATUS_CREATED
+ raise SetFeatureError, "Setting feature flag #{key} to #{value} failed with `#{response}`."
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/scenario/bootable.rb b/qa/qa/scenario/bootable.rb
index dd12ea6d492..038418be023 100644
--- a/qa/qa/scenario/bootable.rb
+++ b/qa/qa/scenario/bootable.rb
@@ -23,7 +23,7 @@ module QA
arguments.parse!(argv)
- self.perform(Runtime::Scenario.attributes, *arguments.default_argv)
+ self.perform(Runtime::Scenario.attributes, *argv)
end
private
@@ -33,7 +33,13 @@ module QA
end
def options
- @options ||= []
+ # Scenario options/attributes are global. There's only ever one
+ # scenario at a time, but they can be inherited and we want scenarios
+ # to share the attributes of their ancestors. For example, `Mattermost`
+ # inherits from `Test::Instance::All` but if this were an instance
+ # variable then `Mattermost` wouldn't have access to the attributes
+ # in `All`
+ @@options ||= [] # rubocop:disable Style/ClassVars
end
def has_attributes?
diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb
new file mode 100644
index 00000000000..40d5c6b1ff1
--- /dev/null
+++ b/qa/qa/scenario/shared_attributes.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module SharedAttributes
+ include Bootable
+
+ attribute :gitlab_address, '--address URL', 'Address of the instance to test'
+ attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests'
+ end
+ end
+end
diff --git a/qa/qa/scenario/template.rb b/qa/qa/scenario/template.rb
index cb1a1de6b9a..b8ea26e805e 100644
--- a/qa/qa/scenario/template.rb
+++ b/qa/qa/scenario/template.rb
@@ -18,19 +18,44 @@ module QA
end
end
- def perform(address, *rspec_options)
- Runtime::Scenario.define(:gitlab_address, address)
+ def perform(options, *args)
+ extract_address(:gitlab_address, options, args)
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
+ Runtime::Feature.enable(options[:enable_feature]) if options.key?(:enable_feature)
+
Specs::Runner.perform do |specs|
specs.tty = true
specs.tags = self.class.focus
- specs.options = rspec_options if rspec_options.any?
+ specs.options = args if args.any?
end
+ ensure
+ Runtime::Feature.disable(options[:enable_feature]) if options.key?(:enable_feature)
+ end
+
+ def extract_option(name, options, args)
+ option = if options.key?(name)
+ options[name]
+ else
+ args.shift
+ end
+
+ Runtime::Scenario.define(name, option)
+
+ option
+ end
+
+ # For backwards-compatibility, if the gitlab instance address is not
+ # specified as an option parsed by OptionParser, it can be specified as
+ # the first argument
+ def extract_address(name, options, args)
+ address = extract_option(name, options, args)
+
+ raise ::ArgumentError, "The address provided for `#{name}` is not valid: #{address}" unless Runtime::Address.valid?(address)
end
end
end
diff --git a/qa/qa/scenario/test/instance/all.rb b/qa/qa/scenario/test/instance/all.rb
index a07c26431bd..168ac4c09a1 100644
--- a/qa/qa/scenario/test/instance/all.rb
+++ b/qa/qa/scenario/test/instance/all.rb
@@ -8,6 +8,7 @@ module QA
module Instance
class All < Template
include Bootable
+ include SharedAttributes
end
end
end
diff --git a/qa/qa/scenario/test/instance/smoke.rb b/qa/qa/scenario/test/instance/smoke.rb
index a7d2cb27f27..43f0623483e 100644
--- a/qa/qa/scenario/test/instance/smoke.rb
+++ b/qa/qa/scenario/test/instance/smoke.rb
@@ -8,6 +8,7 @@ module QA
#
class Smoke < Template
include Bootable
+ include SharedAttributes
tags :smoke
end
diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb
index ece6fba75c9..f5072ee227c 100644
--- a/qa/qa/scenario/test/integration/mattermost.rb
+++ b/qa/qa/scenario/test/integration/mattermost.rb
@@ -9,10 +9,13 @@ module QA
class Mattermost < Test::Instance::All
tags :mattermost
- def perform(address, mattermost, *rspec_options)
- Runtime::Scenario.define(:mattermost_address, mattermost)
+ attribute :mattermost_address, '--mattermost-address URL', 'Address of the Mattermost server'
- super(address, *rspec_options)
+ def perform(options, *args)
+ extract_address(:gitlab_address, options, args)
+ extract_address(:mattermost_address, options, args)
+
+ super(options, *args)
end
end
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 d8609aa037a..d1747280227 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
@@ -14,7 +14,7 @@ module QA
end
project.visit!
- Page::Project::Menu.perform(&:click_members_settings)
+ Page::Project::Menu.perform(&:go_to_members_settings)
Page::Project::Settings::Members.perform do |page|
page.add_member(user.username)
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 3fbcd77dac6..7a36f9ea420 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
@@ -12,7 +12,7 @@ module QA
end
project.visit!
- Page::Project::Menu.perform(&:go_to_settings)
+ Page::Project::Menu.perform(&:click_settings)
Page::Project::Settings::MergeRequest.perform(&:enable_ff_only)
merge_request = Resource::MergeRequest.fabricate! do |merge_request|
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 8e181eb28c6..daeee665c93 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
@@ -57,7 +57,7 @@ module QA
end
it 'branches are correctly listed after CRUD operations' do
- Page::Project::Menu.perform(&:click_repository_branches)
+ Page::Project::Menu.perform(&:go_to_repository_branches)
expect(page).to have_content(master_branch)
expect(page).to have_content(second_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 ff879fdeb16..f41240b7605 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
@@ -16,7 +16,7 @@ module QA
expect(page).to have_content("Title: #{key_title}")
expect(page).to have_content(key.fingerprint)
- Page::Main::Menu.act { go_to_profile_settings }
+ Page::Main::Menu.act { click_settings_link }
Page::Profile::Menu.act { click_ssh_keys }
Page::Profile::SSHKeys.perform do |ssh_keys|
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
index eb59b54e9ab..dc42191b448 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb
@@ -31,7 +31,7 @@ module QA
# Remove the SSH key
login
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ 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/push_mirroring_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
index 99601e3d230..23008a58af8 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb
@@ -21,7 +21,7 @@ module QA
end
source_project_push.project.visit!
- Page::Project::Menu.perform(&:click_repository_settings)
+ Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |settings|
settings.expand_mirroring_repositories do |mirror_settings|
# Configure the source project to push to the target project
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 243f0b83b77..a544efb35ee 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -40,12 +40,12 @@ module QA
set_file_size_limit(1)
expect(page).to have_content("Application settings saved successfully")
- push = push_new_file('oversize_file_2.bin', wait_for_push: false)
- expect(push.output).to have_content 'remote: fatal: pack exceeds maximum allowed size'
+ expect { push_new_file('oversize_file_2.bin', wait_for_push: false) }
+ .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/)
end
def set_file_size_limit(limit)
- Page::Main::Menu.perform(&:go_to_admin_area)
+ Page::Main::Menu.perform(&:click_admin_area)
Page::Admin::Menu.perform(&:go_to_general_settings)
Page::Admin::Settings::General.perform do |setting|
@@ -65,6 +65,7 @@ module QA
p.file_content = SecureRandom.random_bytes(2000000)
p.commit_message = 'Adding a new file'
p.wait_for_push = wait_for_push
+ p.new_branch = false
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
index 7223831d96f..a0251e1c385 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb
@@ -28,7 +28,7 @@ module QA
expect(page).to have_content('README.md')
expect(page).to have_content('Test Use SSH Key')
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |ssh_keys|
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
index b862a7bd1ed..b7400cdca97 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
@@ -33,7 +33,7 @@ module QA
def view_commit
@project.visit!
Page::Project::Show.perform do |page|
- page.go_to_commit(@commit_message)
+ page.click_commit(@commit_message)
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
index ab53dff464e..b643468a664 100644
--- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
@@ -7,7 +7,7 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- Page::Main::Menu.perform(&:go_to_snippets)
+ Page::Main::Menu.perform(&:click_snippets_link)
Resource::Snippet.fabricate_via_browser_ui! do |snippet|
snippet.title = 'Snippet title'
diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
index 29589ec870a..309ae6cd986 100644
--- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb
@@ -20,7 +20,7 @@ module QA
validate_content('My First Wiki Content')
- Page::Project::Wiki::Edit.perform(&:go_to_edit_page)
+ Page::Project::Wiki::Edit.perform(&:click_edit)
Page::Project::Wiki::New.perform do |page|
page.set_content("My Second Wiki Content")
page.save_changes
diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
index 2238d6c382e..7c1d4489c47 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb
@@ -70,7 +70,7 @@ module QA
puts 'Waiting for the runner to process the pipeline'
sleep 15 # Runner should process all jobs within 15 seconds.
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to be_running
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 3f65eabc756..609155da855 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -91,8 +91,8 @@ module QA
sha1sum = Digest::SHA1.hexdigest(gitlab_ci)
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
- Page::Project::Pipeline::Show.perform(&:go_to_first_job)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
+ Page::Project::Pipeline::Show.perform(&:click_on_first_job)
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful
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 0b92ea29ca4..bb1f775da75 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
@@ -63,10 +63,10 @@ module QA
end
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('build')
+ pipeline.click_job('build')
end
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful(timeout: 600)
@@ -75,7 +75,7 @@ module QA
end
Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('test')
+ pipeline.click_job('test')
end
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful(timeout: 600)
@@ -84,7 +84,7 @@ module QA
end
Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('production')
+ pipeline.click_job('production')
end
Page::Project::Job::Show.perform do |job|
expect(job).to be_successful(timeout: 1200)
@@ -92,9 +92,9 @@ module QA
job.click_element(:pipeline_path)
end
- Page::Project::Menu.perform(&:click_operations_environments)
+ Page::Project::Menu.perform(&:go_to_operations_environments)
Page::Project::Operations::Environments::Index.perform do |index|
- index.go_to_environment('production')
+ index.click_environment_link('production')
end
Page::Project::Operations::Environments::Show.perform do |show|
show.view_deployment do
@@ -106,8 +106,7 @@ module QA
end
end
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/87
- describe 'Auto DevOps', :smoke, :quarantine do
+ describe 'Auto DevOps', :smoke do
it 'enables AutoDevOps by default' do
login
@@ -126,7 +125,7 @@ module QA
end
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
+ Page::Project::Pipeline::Index.perform(&:click_on_latest_pipeline)
Page::Project::Pipeline::Show.perform do |pipeline|
expect(pipeline).to have_tag('Auto DevOps')
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 7096864e011..8383dcdb983 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
@@ -9,7 +9,7 @@ module QA
Page::Main::Menu.act { go_to_groups }
Page::Dashboard::Groups.perform do |page|
- page.go_to_new_group
+ page.click_new_group
expect(page).to have_content(
/Create a Mattermost team for this group/
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index 229bfb44fa5..31cff5a241c 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -1,6 +1,9 @@
module QA
module Support
module Api
+ HTTP_STATUS_OK = 200
+ HTTP_STATUS_CREATED = 201
+
def post(url, payload)
RestClient::Request.execute(
method: :post,
diff --git a/qa/qa/tools/delete_subgroups.rb b/qa/qa/tools/delete_subgroups.rb
index c5c48e77ade..3f752adbe6f 100644
--- a/qa/qa/tools/delete_subgroups.rb
+++ b/qa/qa/tools/delete_subgroups.rb
@@ -3,7 +3,7 @@
require_relative '../../qa'
# This script deletes all subgroups of a group specified by ENV['GROUP_NAME_OR_PATH']
-# Required environment variables: PERSONAL_ACCESS_TOKEN and GITLAB_ADDRESS
+# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
# Optional environment variable: GROUP_NAME_OR_PATH (defaults to 'gitlab-qa-sandbox-group')
# Run `rake delete_subgroups`
@@ -14,9 +14,9 @@ module QA
def initialize
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
- raise ArgumentError, "Please provide PERSONAL_ACCESS_TOKEN" unless ENV['PERSONAL_ACCESS_TOKEN']
+ raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN']
- @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['PERSONAL_ACCESS_TOKEN'])
+ @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
end
def run
diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb
index 0a0dbdf5b15..49a1af8e9f0 100644
--- a/qa/qa/tools/generate_perf_testdata.rb
+++ b/qa/qa/tools/generate_perf_testdata.rb
@@ -5,7 +5,7 @@ require 'faker'
require 'yaml'
require_relative '../../qa'
# This script generates testdata for Performance Testing.
-# Required environment variables: PERSONAL_ACCESS_TOKEN and GITLAB_ADDRESS
+# Required environment variables: GITLAB_QA_ACCESS_TOKEN and GITLAB_ADDRESS
# This job creates a urls.txt which contains a hash of all the URLs needed for Performance Testing
# Run `rake generate_perf_testdata`
@@ -16,9 +16,9 @@ module QA
def initialize
raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
- raise ArgumentError, "Please provide PERSONAL_ACCESS_TOKEN" unless ENV['PERSONAL_ACCESS_TOKEN']
+ raise ArgumentError, "Please provide GITLAB_QA_ACCESS_TOKEN" unless ENV['GITLAB_QA_ACCESS_TOKEN']
- @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['PERSONAL_ACCESS_TOKEN'])
+ @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['GITLAB_QA_ACCESS_TOKEN'])
@group_name = "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}"
@project_name = "my-test-project-#{SecureRandom.hex(8)}"
@visibility = "public"
@@ -46,7 +46,7 @@ module QA
threads_arr.each(&:join)
STDOUT.puts "\nURLs: #{@urls}"
- File.open("urls.yml", "w") { |file| file.puts @urls.to_yaml }
+ File.open("urls.yml", "w") { |file| file.puts @urls.stringify_keys.to_yaml }
STDOUT.puts "\nDone"
end
diff --git a/qa/qa/tools/revoke_all_personal_access_tokens.rb b/qa/qa/tools/revoke_all_personal_access_tokens.rb
index 7484b633bf6..8a74f52324d 100644
--- a/qa/qa/tools/revoke_all_personal_access_tokens.rb
+++ b/qa/qa/tools/revoke_all_personal_access_tokens.rb
@@ -27,7 +27,7 @@ module QA
Runtime::Browser.visit(ENV['GITLAB_ADDRESS'], Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_access_tokens)
token_name = 'api-test-token'
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index fc51f45c3a1..04085efe2ce 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -90,13 +90,13 @@ describe QA::Runtime::Env do
described_class.instance_variable_set(:@personal_access_token, nil)
end
- context 'when PERSONAL_ACCESS_TOKEN is set' do
+ context 'when GITLAB_QA_ACCESS_TOKEN is set' do
before do
- stub_env('PERSONAL_ACCESS_TOKEN', 'a_token')
+ stub_env('GITLAB_QA_ACCESS_TOKEN', 'a_token_too')
end
it 'returns specified token from env' do
- expect(described_class.personal_access_token).to eq 'a_token'
+ expect(described_class.personal_access_token).to eq 'a_token_too'
end
end
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb
new file mode 100644
index 00000000000..192299b7857
--- /dev/null
+++ b/qa/spec/runtime/feature_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+describe QA::Runtime::Feature do
+ let(:api_client) { double('QA::Runtime::API::Client') }
+ let(:request) { Struct.new(:url).new('http://api') }
+ let(:response) { Struct.new(:code).new(201) }
+
+ before do
+ allow(described_class).to receive(:api_client).and_return(api_client)
+ end
+
+ describe '.enable' do
+ it 'enables a feature flag' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features/a-flag")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:post)
+ .with(request.url, { value: true })
+ .and_return(response)
+
+ subject.enable('a-flag')
+ end
+ end
+
+ describe '.disable' do
+ it 'disables a feature flag' do
+ expect(QA::Runtime::API::Request)
+ .to receive(:new)
+ .with(api_client, "/features/a-flag")
+ .and_return(request)
+ expect(described_class)
+ .to receive(:post)
+ .with(request.url, { value: false })
+ .and_return(response)
+
+ subject.disable('a-flag')
+ end
+ end
+end
diff --git a/qa/spec/runtime/scenario_spec.rb b/qa/spec/runtime/scenario_spec.rb
index 7009192bcc0..70fc71ffc02 100644
--- a/qa/spec/runtime/scenario_spec.rb
+++ b/qa/spec/runtime/scenario_spec.rb
@@ -13,6 +13,14 @@ describe QA::Runtime::Scenario do
.to eq(my_attribute: 'some-value', another_attribute: 'another-value')
end
+ it 'replaces an existing attribute' do
+ subject.define(:my_attribute, 'some-value')
+ subject.define(:my_attribute, 'another-value')
+
+ expect(subject.my_attribute).to eq 'another-value'
+ expect(subject.attributes).to eq(my_attribute: 'another-value')
+ end
+
it 'raises error when attribute is not known' do
expect { subject.invalid_accessor }
.to raise_error ArgumentError, /invalid_accessor/
diff --git a/qa/spec/scenario/bootable_spec.rb b/qa/spec/scenario/bootable_spec.rb
index 273aac7677e..bd89b21f7fb 100644
--- a/qa/spec/scenario/bootable_spec.rb
+++ b/qa/spec/scenario/bootable_spec.rb
@@ -4,14 +4,21 @@ describe QA::Scenario::Bootable do
.include(described_class)
end
+ before do
+ allow(subject).to receive(:options).and_return([])
+ allow(QA::Runtime::Scenario).to receive(:attributes).and_return({})
+ end
+
it 'makes it possible to define the scenario attribute' do
subject.class_eval do
attribute :something, '--something SOMETHING', 'Some attribute'
attribute :another, '--another ANOTHER', 'Some other attribute'
end
+ # If we run just this test from the command line it fails unless
+ # we include the command line args that we use to select this test.
expect(subject).to receive(:perform)
- .with(something: 'test', another: 'other')
+ .with({ something: 'test', another: 'other' })
subject.launch!(%w[--another other --something test])
end
diff --git a/qa/spec/scenario/template_spec.rb b/qa/spec/scenario/template_spec.rb
new file mode 100644
index 00000000000..f97fc22daf9
--- /dev/null
+++ b/qa/spec/scenario/template_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+describe QA::Scenario::Template do
+ let(:feature) { spy('Runtime::Feature') }
+ let(:release) { spy('Runtime::Release') }
+
+ before do
+ stub_const('QA::Runtime::Release', release)
+ stub_const('QA::Runtime::Feature', feature)
+ allow(QA::Specs::Runner).to receive(:perform)
+ allow(QA::Runtime::Address).to receive(:valid?).and_return(true)
+ end
+
+ it 'allows a feature to be enabled' do
+ subject.perform({ enable_feature: 'a-feature' })
+
+ expect(feature).to have_received(:enable).with('a-feature')
+ end
+
+ it 'ensures an enabled feature is disabled afterwards' do
+ allow(QA::Specs::Runner).to receive(:perform).and_raise('failed test')
+
+ expect { subject.perform({ enable_feature: 'a-feature' }) }.to raise_error('failed test')
+
+ expect(feature).to have_received(:enable).with('a-feature')
+ expect(feature).to have_received(:disable).with('a-feature')
+ end
+end
diff --git a/qa/spec/scenario/test/integration/github_spec.rb b/qa/spec/scenario/test/integration/github_spec.rb
index c2aeb1ded1d..6112ba7c694 100644
--- a/qa/spec/scenario/test/integration/github_spec.rb
+++ b/qa/spec/scenario/test/integration/github_spec.rb
@@ -12,7 +12,7 @@ describe QA::Scenario::Test::Integration::Github do
let(:tags) { [:github] }
it 'requires a GitHub access token' do
- subject.perform('gitlab_address')
+ subject.perform(args)
expect(env).to have_received(:require_github_access_token!)
end
diff --git a/qa/spec/scenario/test/integration/mattermost_spec.rb b/qa/spec/scenario/test/integration/mattermost_spec.rb
index 59caf2ba2cd..4e75e72f4d2 100644
--- a/qa/spec/scenario/test/integration/mattermost_spec.rb
+++ b/qa/spec/scenario/test/integration/mattermost_spec.rb
@@ -4,14 +4,21 @@ describe QA::Scenario::Test::Integration::Mattermost do
context '#perform' do
it_behaves_like 'a QA scenario class' do
let(:args) { %w[gitlab_address mattermost_address] }
+ let(:args) do
+ {
+ gitlab_address: 'http://gitlab_address',
+ mattermost_address: 'http://mattermost_address'
+ }
+ end
+ let(:named_options) { %w[--address http://gitlab_address --mattermost-address http://mattermost_address] }
let(:tags) { [:mattermost] }
let(:options) { ['path1']}
it 'requires a GitHub access token' do
- subject.perform(*args)
+ subject.perform(args)
expect(attributes).to have_received(:define)
- .with(:mattermost_address, 'mattermost_address')
+ .with(:mattermost_address, 'http://mattermost_address')
end
end
end
diff --git a/qa/spec/shared_examples/scenario_shared_examples.rb b/qa/spec/shared_examples/scenario_shared_examples.rb
index 5fd55d7d96b..697e6cb39c8 100644
--- a/qa/spec/shared_examples/scenario_shared_examples.rb
+++ b/qa/spec/shared_examples/scenario_shared_examples.rb
@@ -2,19 +2,23 @@
shared_examples 'a QA scenario class' do
let(:attributes) { spy('Runtime::Scenario') }
- let(:release) { spy('Runtime::Release') }
let(:runner) { spy('Specs::Runner') }
+ let(:release) { spy('Runtime::Release') }
+ let(:feature) { spy('Runtime::Feature') }
- let(:args) { ['gitlab_address'] }
+ let(:args) { { gitlab_address: 'http://gitlab_address' } }
+ let(:named_options) { %w[--address http://gitlab_address] }
let(:tags) { [] }
let(:options) { %w[path1 path2] }
before do
+ stub_const('QA::Specs::Runner', runner)
stub_const('QA::Runtime::Release', release)
stub_const('QA::Runtime::Scenario', attributes)
- stub_const('QA::Specs::Runner', runner)
+ stub_const('QA::Runtime::Feature', feature)
allow(runner).to receive(:perform).and_yield(runner)
+ allow(QA::Runtime::Address).to receive(:valid?).and_return(true)
end
it 'responds to perform' do
@@ -22,28 +26,48 @@ shared_examples 'a QA scenario class' do
end
it 'sets an address of the subject' do
- subject.perform(*args)
+ subject.perform(args)
- expect(attributes).to have_received(:define).with(:gitlab_address, 'gitlab_address')
+ expect(attributes).to have_received(:define).with(:gitlab_address, 'http://gitlab_address').at_least(:once)
end
it 'performs before hooks' do
- subject.perform(*args)
+ subject.perform(args)
expect(release).to have_received(:perform_before_hooks)
end
it 'sets tags on runner' do
- subject.perform(*args)
+ subject.perform(args)
expect(runner).to have_received(:tags=).with(tags)
end
context 'specifying RSpec options' do
it 'sets options on runner' do
- subject.perform(*args, *options)
+ subject.perform(args, *options)
expect(runner).to have_received(:options=).with(options)
end
end
+
+ context 'with named command-line options' do
+ it 'converts options to attributes' do
+ described_class.launch!(named_options)
+
+ args do |k, v|
+ expect(attributes).to have_received(:define).with(k, v)
+ end
+ end
+
+ it 'raises an error if the option is invalid' do
+ expect { described_class.launch!(['--foo']) }.to raise_error(OptionParser::InvalidOption)
+ end
+
+ it 'passes on options after --' do
+ expect(described_class).to receive(:perform).with(attributes, *%w[--tag quarantine])
+
+ described_class.launch!(named_options.push(*%w[-- --tag quarantine]))
+ end
+ end
end
diff --git a/rubocop/cop/migration/update_column_in_batches.rb b/rubocop/cop/migration/update_column_in_batches.rb
index 5461abc5ee0..b1c43393f6a 100644
--- a/rubocop/cop/migration/update_column_in_batches.rb
+++ b/rubocop/cop/migration/update_column_in_batches.rb
@@ -28,7 +28,7 @@ module RuboCop
source_name = node.location.expression.source_buffer.name
path = Pathname.new(source_name).relative_path_from(rails_root)
dirname = File.dirname(path)
- .sub(%r{\Adb/(migrate|post_migrate)}, 'spec/migrations')
+ .sub(%r{db/(migrate|post_migrate)}, 'spec/migrations')
filename = File.basename(source_name, '.rb').sub(/\A\d+_/, '')
File.join(dirname, "#{filename}_spec.rb")
diff --git a/scripts/frontend/stylelint/stylelint-duplicate-selectors.js b/scripts/frontend/stylelint/stylelint-duplicate-selectors.js
new file mode 100644
index 00000000000..4b46ee21a7a
--- /dev/null
+++ b/scripts/frontend/stylelint/stylelint-duplicate-selectors.js
@@ -0,0 +1,23 @@
+const stylelint = require('stylelint');
+const utils = require('./stylelint-utils');
+const ruleName = 'stylelint-gitlab/duplicate-selectors';
+
+const messages = stylelint.utils.ruleMessages(ruleName, {
+ expected: (selector1, selector2) => {
+ return `"${selector1}" and "${selector2}" have the same properties.`;
+ },
+});
+
+module.exports = stylelint.createPlugin(ruleName, function(enabled) {
+ if (!enabled) {
+ return;
+ }
+
+ return function(root, result) {
+ const selectorGroups = {};
+ utils.createPropertiesHashmap(root, result, ruleName, messages, selectorGroups, true);
+ };
+});
+
+module.exports.ruleName = ruleName;
+module.exports.messages = messages;
diff --git a/scripts/frontend/stylelint/stylelint-utility-classes.js b/scripts/frontend/stylelint/stylelint-utility-classes.js
new file mode 100644
index 00000000000..8a1cfdbf302
--- /dev/null
+++ b/scripts/frontend/stylelint/stylelint-utility-classes.js
@@ -0,0 +1,24 @@
+const stylelint = require('stylelint');
+const utils = require('./stylelint-utils');
+const utilityClasses = require('./utility-classes-map.js');
+
+const ruleName = 'stylelint-gitlab/utility-classes';
+
+const messages = stylelint.utils.ruleMessages(ruleName, {
+ expected: (selector1, selector2) => {
+ return `"${selector1}" has the same properties as our BS4 utility class "${selector2}" so please use that instead.`;
+ },
+});
+
+module.exports = stylelint.createPlugin(ruleName, function(enabled) {
+ if (!enabled) {
+ return;
+ }
+
+ return function(root, result) {
+ utils.createPropertiesHashmap(root, result, ruleName, messages, utilityClasses, false);
+ };
+});
+
+module.exports.ruleName = ruleName;
+module.exports.messages = messages;
diff --git a/scripts/frontend/stylelint/stylelint-utility-map.js b/scripts/frontend/stylelint/stylelint-utility-map.js
new file mode 100644
index 00000000000..7e012b157b3
--- /dev/null
+++ b/scripts/frontend/stylelint/stylelint-utility-map.js
@@ -0,0 +1,54 @@
+const sass = require('node-sass');
+const postcss = require('postcss');
+const fs = require('fs');
+const path = require('path');
+const prettier = require('prettier');
+
+const utils = require('./stylelint-utils');
+const ROOT_PATH = path.resolve(__dirname, '../../..');
+const hashMapPath = path.resolve(__dirname, './utility-classes-map.js');
+
+//
+// This creates a JS based hash map (saved in utility-classes-map.js) of the different values in the utility classes
+//
+sass.render(
+ {
+ data: `
+ @import './functions';
+ @import './variables';
+ @import './mixins';
+ @import './utilities';
+ `,
+ includePaths: [path.resolve(ROOT_PATH, 'node_modules/bootstrap/scss')],
+ },
+ (err, result) => {
+ if (err) console.error('Error ', err);
+
+ const cssResult = result.css.toString();
+
+ // We just use postcss to create a CSS tree
+ postcss([])
+ .process(cssResult, {
+ // This supresses a postcss warning
+ from: undefined,
+ })
+ .then(result => {
+ const selectorGroups = {};
+ utils.createPropertiesHashmap(result.root, result, null, null, selectorGroups, true);
+
+ const prettierOptions = prettier.resolveConfig.sync(hashMapPath);
+ const prettyHashmap = prettier.format(
+ `module.exports = ${JSON.stringify(selectorGroups)};`,
+ prettierOptions,
+ );
+
+ fs.writeFile(hashMapPath, prettyHashmap, function(err) {
+ if (err) {
+ return console.log(err);
+ }
+
+ console.log('The file was saved!');
+ });
+ });
+ },
+);
diff --git a/scripts/frontend/stylelint/stylelint-utils.js b/scripts/frontend/stylelint/stylelint-utils.js
new file mode 100644
index 00000000000..2d931c1c4c2
--- /dev/null
+++ b/scripts/frontend/stylelint/stylelint-utils.js
@@ -0,0 +1,77 @@
+const stylelint = require('stylelint');
+const md5 = require('md5');
+
+module.exports.createPropertiesHashmap = (
+ ruleRoot,
+ result,
+ ruleName,
+ messages,
+ selectorGroups,
+ addSelectors,
+) => {
+ ruleRoot.walkRules(rule => {
+ const selector = rule.selector.replace(/(?:\r\n|\r|\n)/g, ' ');
+
+ if (
+ rule &&
+ rule.parent &&
+ rule.parent.type != 'atrule' &&
+ !(
+ selector.includes('-webkit-') ||
+ selector.includes('-moz-') ||
+ selector.includes('-o-') ||
+ selector.includes('-ms-') ||
+ selector.includes(':')
+ )
+ ) {
+ let cssArray = [];
+ rule.nodes.forEach(function(property) {
+ const { prop, value } = property;
+ if (property && value) {
+ const propval = `${prop}${value}${property.important ? '!important' : ''}`;
+ cssArray.push(propval);
+ }
+ });
+
+ cssArray = cssArray.sort();
+ const cssContent = cssArray.toString();
+
+ if (cssContent) {
+ const hashValue = md5(cssContent);
+ const selObj = selectorGroups[hashValue];
+
+ const selectorLine = `${selector} (${
+ rule.source.input.file ? rule.source.input.file + ' -' : ''
+ }${rule.source.start.line}:${rule.source.start.column})`;
+
+ if (selObj) {
+ if (selectorGroups[hashValue].selectors.indexOf(selector) == -1) {
+ let lastSelector =
+ selectorGroups[hashValue].selectors[selectorGroups[hashValue].selectors.length - 1];
+
+ // So we have nicer formatting if it is the same file, we remove the filename
+ lastSelector = lastSelector.replace(`${rule.source.input.file} - `, '');
+
+ if (messages) {
+ stylelint.utils.report({
+ result,
+ ruleName,
+ message: messages.expected(selector, lastSelector),
+ node: rule,
+ word: rule.node,
+ });
+ }
+
+ if (addSelectors) {
+ selectorGroups[hashValue].selectors.push(selectorLine);
+ }
+ }
+ } else if (addSelectors) {
+ selectorGroups[hashValue] = {
+ selectors: [selectorLine],
+ };
+ }
+ }
+ }
+ });
+};
diff --git a/scripts/frontend/stylelint/utility-classes-map.js b/scripts/frontend/stylelint/utility-classes-map.js
new file mode 100644
index 00000000000..1929f950f6c
--- /dev/null
+++ b/scripts/frontend/stylelint/utility-classes-map.js
@@ -0,0 +1,216 @@
+module.exports = {
+ '99097f29a9473b56eacdb9ff0681c366': { selectors: ['.align-baseline (1:1)'] },
+ d969b318bb994e104e8c965006d71cb7: { selectors: ['.align-top (4:1)'] },
+ '8cd54ab97b9cc43cb9d13d2ea7c601c7': { selectors: ['.align-middle (7:1)'] },
+ dd06eb6c49e979b7a9fdaa7119aa0a0b: { selectors: ['.align-bottom (10:1)'] },
+ '0af1e90cbc468615e299ec9f49e97c4a': { selectors: ['.align-text-bottom (13:1)'] },
+ '50af706df238cf59bdc634fc684ba0c9': { selectors: ['.align-text-top (16:1)'] },
+ c968922e6e47445362129a684b5913c0: { selectors: ['.bg-primary (19:1)'] },
+ '3c397f9786c24cff4779a11cf5b3d7e7': { selectors: ['.bg-secondary (27:1)'] },
+ '659677469a4477267fabc1788f7cad4e': { selectors: ['.bg-success (35:1)'] },
+ '56d246d5b6a708a4c6f78dbd2444106c': { selectors: ['.bg-info (43:1)'] },
+ '6bec0a33df3a6380c30103db5c273455': { selectors: ['.bg-warning (51:1)'] },
+ '0ce5d074c8667ce6c32360658f428d5d': { selectors: ['.bg-danger (59:1)'] },
+ '0d0269c62a01e97caa9039d227a25d12': { selectors: ['.bg-light (67:1)'] },
+ '3a56309ad8c5b46ebcc3b13fe1987ac1': { selectors: ['.bg-dark (75:1)'] },
+ '0e252f8dd392a33343d3d5efc1e3194a': { selectors: ['.bg-white (83:1)'] },
+ '3af6f52f0ed4f98e797d5f10a35ca6bc': { selectors: ['.bg-transparent (86:1)'] },
+ '16da7fdce74577ceab356509db565612': { selectors: ['.border (89:1)'] },
+ '929622517ca05efde3b51e5f1a57064e': { selectors: ['.border-top (92:1)'] },
+ '7283090353df54f1d515a6ceddfb9693': { selectors: ['.border-right (95:1)'] },
+ bd5670d71332c652b46db82949042e31: { selectors: ['.border-bottom (98:1)'] },
+ fa71e003d04734a898a85cc5285e3cbb: { selectors: ['.border-left (101:1)'] },
+ ed482cea071e316f29d78fd93c3f3644: { selectors: ['.border-0 (104:1)'] },
+ '90cb661baf21e10be6e479cb0544b1a7': { selectors: ['.border-top-0 (107:1)'] },
+ '8a32707eaa09fc998bf8cc915710b60c': { selectors: ['.border-right-0 (110:1)'] },
+ a6f01957e142a000e7742b31ac6c2331: { selectors: ['.border-bottom-0 (113:1)'] },
+ c740fe952cc1985ee14f7d1c7a359a29: { selectors: ['.border-left-0 (116:1)'] },
+ af9dd93e9780306ffa4bb25a6384902f: { selectors: ['.border-primary (119:1)'] },
+ afa290dfe58cca06be5924ceae1b019b: { selectors: ['.border-secondary (122:1)'] },
+ '9b1ac460bdddf1e0164d7bf988cc2da8': { selectors: ['.border-success (125:1)'] },
+ '091cbf41d6be12061382fa571ee1ce82': { selectors: ['.border-info (128:1)'] },
+ '3ada321d4a387901dad6d80e1b6be3fd': { selectors: ['.border-warning (131:1)'] },
+ '13b4713dd52c1e359d1b43dd658cb249': { selectors: ['.border-danger (134:1)'] },
+ '0048e110875ea22b04104d55e764a367': { selectors: ['.border-light (137:1)'] },
+ a900b6b567c9a911326cdd0e19f40f8e: { selectors: ['.border-dark (140:1)'] },
+ '78bcd867ac9677c743c2bc33b872f27b': { selectors: ['.border-white (143:1)'] },
+ e0fc10c49c7b7f4d1924336d21a4f64e: { selectors: ['.rounded (146:1)'] },
+ '1b74b9d0a7d6a59281b5b5cae43c859a': { selectors: ['.rounded-top (149:1)'] },
+ '20b75f55f39e662e038d51a6442c03df': { selectors: ['.rounded-right (153:1)'] },
+ '83ea6db794873239c21f44af25618677': { selectors: ['.rounded-bottom (157:1)'] },
+ '8464e9e8001e65dfc06397436a5eebd7': { selectors: ['.rounded-left (161:1)'] },
+ '59c2f788287fa43caf5891adfc5c796e': { selectors: ['.rounded-circle (165:1)'] },
+ '31a632ba94f8c41558bd6044458f1459': { selectors: ['.rounded-0 (168:1)'] },
+ '16aaf53ab29d6b248b0257f2fa413914': { selectors: ['.d-none (176:1)'] },
+ '4f42736ac9217039ed791b4306e60aeb': { selectors: ['.d-inline (179:1)'] },
+ '067efa04b76649e8afcdceb9f5f7e870': { selectors: ['.d-inline-block (182:1)'] },
+ de54f49149fb9b512aa79ad9ada838f2: { selectors: ['.d-block (185:1)'] },
+ '80fc32acbc0c28ee890a160c23529d26': { selectors: ['.d-table (188:1)'] },
+ '6a87b1db48298ca94cbe5dee79a6eed1': { selectors: ['.d-table-row (191:1)'] },
+ b9896f0d94760bf5920f47904e9f7512: { selectors: ['.d-table-cell (194:1)'] },
+ d25c51f38c4d057209b96c664de68c44: { selectors: ['.d-flex (197:1)'] },
+ e72d46b636d5b8e17e771daa95793f33: { selectors: ['.d-inline-flex (200:1)'] },
+ '2c433b7c14a5ae32cfa8ec7867ee8526': { selectors: ['.embed-responsive (303:1)'] },
+ '56b318b8d8eb845b769d60cefcd131bb': {
+ selectors: [
+ '.embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video (312:3)',
+ ],
+ },
+ c5009af89633c4d2f71a0a9fa333630d: { selectors: ['.flex-row (337:1)'] },
+ '7b06a3d956579cd64b6f5b1a57255369': { selectors: ['.flex-column (340:1)'] },
+ '21744a8c4dc6ed1519903b4236b00af4': { selectors: ['.flex-row-reverse (343:1)'] },
+ '18d903735f9c71070b6d8166aa1112f1': { selectors: ['.flex-column-reverse (346:1)'] },
+ e2a57aa8196347d4da84f33a4f551325: { selectors: ['.flex-wrap (349:1)'] },
+ b6b29f75b174b5609f9e7d5eef457b70: { selectors: ['.flex-nowrap (352:1)'] },
+ '839230fc7c0abacb6418b49d8f10b27f': { selectors: ['.flex-wrap-reverse (355:1)'] },
+ '924d9b261944a8e8ff684d5b519062bb': { selectors: ['.flex-fill (358:1)'] },
+ '5ed396aeb08464b7df8fc37d29455319': { selectors: ['.flex-grow-0 (361:1)'] },
+ '94251dc4d012339a3e37df6196fc79bb': { selectors: ['.flex-grow-1 (364:1)'] },
+ '0eeed7dabca0452a46574776a4485e6e': { selectors: ['.flex-shrink-0 (367:1)'] },
+ c69e60d5e51a1b74d22b172ab98ef9d5: { selectors: ['.flex-shrink-1 (370:1)'] },
+ '03401c1a81eb6d4639f020f76dd60176': { selectors: ['.justify-content-start (373:1)'] },
+ '39e3d2c344e78869c98ef099249e717d': { selectors: ['.justify-content-end (376:1)'] },
+ '3b2d00c0bea857ab78a71b0872842980': { selectors: ['.justify-content-center (379:1)'] },
+ b1c3c9edd20ed7c08b43863d38ebee40: { selectors: ['.justify-content-between (382:1)'] },
+ a11b4b1d983c5fa75777f273e998f73e: { selectors: ['.justify-content-around (385:1)'] },
+ '50e33f29f65bfffa6a3591fcb6045ca9': { selectors: ['.align-items-start (388:1)'] },
+ e44b276e47415ec19b74cc16740ada1d: { selectors: ['.align-items-end (391:1)'] },
+ '4a9d2716bca651758564059dceed1271': { selectors: ['.align-items-center (394:1)'] },
+ fd970b017f7f558f30cb273bf71ede7d: { selectors: ['.align-items-baseline (397:1)'] },
+ '7204b6b00b69f8af1e4a24c9b6e7f7f9': { selectors: ['.align-items-stretch (400:1)'] },
+ '350fbb74eb70bd05f9438067c3990e9f': { selectors: ['.align-content-start (403:1)'] },
+ '74d61397b4fcbf608f3dba39ab3b2a1b': { selectors: ['.align-content-end (406:1)'] },
+ eed1ab4ee9e5b327a434512176741548: { selectors: ['.align-content-center (409:1)'] },
+ '19878ab832978ef7e1746ac2fe4084b2': { selectors: ['.align-content-between (412:1)'] },
+ dded333d0522692809517039f5a727c1: { selectors: ['.align-content-around (415:1)'] },
+ '5cbb83ea2af7e9db8ef13f4c7d6db875': { selectors: ['.align-content-stretch (418:1)'] },
+ dc3df46d3c023184d375a63a71916646: { selectors: ['.align-self-auto (421:1)'] },
+ '0c6d2d8c9732c571f9cf61a4b1d2877f': { selectors: ['.align-self-start (424:1)'] },
+ fe7c6071e3e17214df1bdd38850d9ff0: { selectors: ['.align-self-end (427:1)'] },
+ '9611bbad74d72d50cf238088576a5089': { selectors: ['.align-self-center (430:1)'] },
+ '4bc5edddf5981866946175bfedb7247f': { selectors: ['.align-self-baseline (433:1)'] },
+ '4fffdd27ec60120ec9ed16fd7feef801': { selectors: ['.align-self-stretch (436:1)'] },
+ '39e658501f5502b35919f02fa9591360': { selectors: ['.float-left (719:1)'] },
+ b51436c537ffc4269b1533e44d7c3467: { selectors: ['.float-right (722:1)'] },
+ c4597a87d2c793a6d696cfe06f6c95ce: { selectors: ['.float-none (725:1)'] },
+ aaf8dc6e0768e59f3098a98a5c144d66: { selectors: ['.position-static (760:1)'] },
+ '79592de2383045d15ab57d35aa1dab95': { selectors: ['.position-relative (763:1)'] },
+ a7c272f53d0368730bde4c2740ffb5c3: { selectors: ['.position-absolute (766:1)'] },
+ dad0bb92d53f17cf8affc10f77824b7f: { selectors: ['.position-fixed (769:1)'] },
+ '6da0f6a7354a75fe6c95b08a4cabc06f': { selectors: ['.position-sticky (772:1)'] },
+ '66602e21eea7b673883155c8f42b9590': { selectors: ['.fixed-top (775:1)'] },
+ '33ea70eb6db7f6ab3469680f182abb19': { selectors: ['.fixed-bottom (782:1)'] },
+ '405e9dd7c9919943af14658313fd31e4': { selectors: ['.sr-only (795:1)'] },
+ '9220ad156a70c2586b15fe2b9b6108b2': { selectors: ['.shadow-sm (813:1)'] },
+ b1b8c0ff70767ca2160811a026766982: { selectors: ['.shadow (816:1)'] },
+ da0792abe99964acb6692a03c61d6dd8: { selectors: ['.shadow-lg (819:1)'] },
+ bb2173057af1cf20e687469b2d9cbb3c: { selectors: ['.shadow-none (822:1)'] },
+ '6d8abb6519a186483b25429ab8b9765e': { selectors: ['.w-25 (825:1)'] },
+ a087c1ffdf8ead76cdd37445b414d63e: { selectors: ['.w-50 (828:1)'] },
+ '28180742013a90275be5633e6ec0dd51': { selectors: ['.w-75 (831:1)'] },
+ '195a03bc95a0af0ba6c8824db97a0b2f': { selectors: ['.w-100 (834:1)'] },
+ e67c74b650d6236b03be9dfc10c78e32: { selectors: ['.w-auto (837:1)'] },
+ c1b6262b3ee069addc1fbe46f64aac4e: { selectors: ['.h-25 (840:1)'] },
+ a520396ae349bef86145e0761aa0699e: { selectors: ['.h-50 (843:1)'] },
+ '7c53b57d54beb087fd7ab8b669c5fe60': { selectors: ['.h-75 (846:1)'] },
+ ad74f1972cb745b7a78b03e16a387f21: { selectors: ['.h-100 (849:1)'] },
+ '2cd49c3d63d260ba4f0b23c559ad05e0': { selectors: ['.h-auto (852:1)'] },
+ '0b43071a67efc45ee1735fdc2491313c': { selectors: ['.mw-100 (855:1)'] },
+ eac31a6f08e5c935e24b97df0fdad579: { selectors: ['.mh-100 (858:1)'] },
+ cfdb4f497b16074959bfd3deb7ea9c42: { selectors: ['.m-0 (861:1)'] },
+ '4d666c270ba50524312d97c4b937d153': { selectors: ['.mt-0, .my-0 (864:1)'] },
+ eccf47ccd76ceffb4b139cb6c080b5ac: { selectors: ['.mr-0, .mx-0 (868:1)'] },
+ '9bc513e73c0bdc6efdf170cb31de16d1': { selectors: ['.mb-0, .my-0 (872:1)'] },
+ e99cfc55b03f0e67f11628b19889ad7b: { selectors: ['.ml-0, .mx-0 (876:1)'] },
+ e1c39484d90d2acaa00973531f47f738: { selectors: ['.m-1 (880:1)'] },
+ '63791bc02eccfdfa2c01621a801e565f': { selectors: ['.mt-1, .my-1 (883:1)'] },
+ bcb27ab9d7dcfdd0d7cacad02709966c: { selectors: ['.mr-1, .mx-1 (887:1)'] },
+ cb5d1c4328e25b5bc93be9a252973690: { selectors: ['.mb-1, .my-1 (891:1)'] },
+ b80b1010c7dcfbb30bed9015c4f2e969: { selectors: ['.ml-1, .mx-1 (895:1)'] },
+ ecab1c9cdf8a562e3c0f70307aeafa89: { selectors: ['.m-2 (899:1)'] },
+ '6505fe17fbbd88b1884113a754aa82ab': { selectors: ['.mt-2, .my-2 (902:1)'] },
+ '6f0c7d09d1e729f332c4671ccc2b48c0': { selectors: ['.mr-2, .mx-2 (906:1)'] },
+ '70ef7b668b382b3c747b2d73e08cdbed': { selectors: ['.mb-2, .my-2 (910:1)'] },
+ '2d7f277cc78ed324a8fc1f71ab281e1f': { selectors: ['.ml-2, .mx-2 (914:1)'] },
+ '8ebcfe52fd4024861082ffb1735747a7': { selectors: ['.m-3 (918:1)'] },
+ '9965fb516bdb72b87023a533123a8035': { selectors: ['.mt-3, .my-3 (921:1)'] },
+ b1fcbbb1dc6226f6da6000830088e051: { selectors: ['.mr-3, .mx-3 (925:1)'] },
+ '02204826cfbe3da98535c0d802870940': { selectors: ['.mb-3, .my-3 (929:1)'] },
+ '0259859060250ae6b730218733e7a437': { selectors: ['.ml-3, .mx-3 (933:1)'] },
+ '8cf300dab2a4994a105eeddda826f2e6': { selectors: ['.m-4 (937:1)'] },
+ '1ba62fdddd3349f52a452050688905c7': { selectors: ['.mt-4, .my-4 (940:1)'] },
+ '66a104129fa13db5a0829567fba6ee41': { selectors: ['.mr-4, .mx-4 (944:1)'] },
+ eefcc4c10b79e70e8e8a5a66fb2b7aa1: { selectors: ['.mb-4, .my-4 (948:1)'] },
+ eb1503656dc920d15a31116956fdffa4: { selectors: ['.ml-4, .mx-4 (952:1)'] },
+ '79cbb6e5c9b73fd0be29d4fc5733a099': { selectors: ['.m-5 (956:1)'] },
+ '67d8671699df706a428e7da42a7141cb': { selectors: ['.mt-5, .my-5 (959:1)'] },
+ e9cb4a0a8a60ff018c87a0b7efa9de29: { selectors: ['.mr-5, .mx-5 (963:1)'] },
+ '93f579214354dbd8cb60209c068f0086': { selectors: ['.mb-5, .my-5 (967:1)'] },
+ '2a789d4af97d2b87fd0bf2b4626120cd': { selectors: ['.ml-5, .mx-5 (971:1)'] },
+ '64a89d28e8287c1a0ac153001082644c': { selectors: ['.p-0 (975:1)'] },
+ b03aa6db5ddf110bbdbefbbec43fda30: { selectors: ['.pt-0, .py-0 (978:1)'] },
+ e38192ca32a98888d4c4876880f4fece: { selectors: ['.pr-0, .px-0 (982:1)'] },
+ '70fe8ef50e999ddd29506f672c107069': { selectors: ['.pb-0, .py-0 (986:1)'] },
+ '9355e8cd9109049726475ba356661bcf': { selectors: ['.pl-0, .px-0 (990:1)'] },
+ '0d4c53468c2658c5324b9ec7a8ca6de2': { selectors: ['.p-1 (994:1)'] },
+ d74e430b2a56b3a4e20065c972b7fa3f: { selectors: ['.pt-1, .py-1 (997:1)'] },
+ '21e4644967aedd19888b6f4a700b629b': { selectors: ['.pr-1, .px-1 (1001:1)'] },
+ e315a7b9b7a1d0df3ea7d95af5203a0b: { selectors: ['.pb-1, .py-1 (1005:1)'] },
+ '14630ca122e1d9830a9ef5591c4097d0': { selectors: ['.pl-1, .px-1 (1009:1)'] },
+ '5b1c65e5139e86e5f4755824f8b77d13': { selectors: ['.p-2 (1013:1)'] },
+ '244af70950a1e200d3849f75ce51d707': { selectors: ['.pt-2, .py-2 (1016:1)'] },
+ b583832738cad724c7c23e5c14ac9bfb: { selectors: ['.pr-2, .px-2 (1020:1)'] },
+ e1e633c4f1375e8276154192d8899e39: { selectors: ['.pb-2, .py-2 (1024:1)'] },
+ '676b01e25f0dbb3f7d2f2529231cda08': { selectors: ['.pl-2, .px-2 (1028:1)'] },
+ '9b5165e3333b22801f2287f7983d7516': { selectors: ['.p-3 (1032:1)'] },
+ '5bcaa9df87a507f6cd14659ea176bdc5': { selectors: ['.pt-3, .py-3 (1035:1)'] },
+ f706637180776c5589385599705a2409: { selectors: ['.pr-3, .px-3 (1039:1)'] },
+ '41157cfbcf47990b383b5b0379386ab2': { selectors: ['.pb-3, .py-3 (1043:1)'] },
+ cac1e7a204bb6a1f42707b684ad46238: { selectors: ['.pl-3, .px-3 (1047:1)'] },
+ '43e0671cd41a4b7590284888b607a134': { selectors: ['.p-4 (1051:1)'] },
+ '116b0f95ebde1ff8907e488413a88854': { selectors: ['.pt-4, .py-4 (1054:1)'] },
+ ecb06765fe691d892df000eebbb23dcc: { selectors: ['.pr-4, .px-4 (1058:1)'] },
+ '1331503a48d36025c861e660bc615048': { selectors: ['.pb-4, .py-4 (1062:1)'] },
+ f8665f7e547e499abd7ac63813b274f5: { selectors: ['.pl-4, .px-4 (1066:1)'] },
+ '4160a315459f1b5a98255863f42136fe': { selectors: ['.p-5 (1070:1)'] },
+ f55a6b2de6a434ec7b4375f06f4fad75: { selectors: ['.pt-5, .py-5 (1073:1)'] },
+ '19391dc45c8d7730a86d521c28f52c3f': { selectors: ['.pr-5, .px-5 (1077:1)'] },
+ '15898bcb7ff74a60006f9931422b4ad3': { selectors: ['.pb-5, .py-5 (1081:1)'] },
+ '6290bdc6355aed1e9b27379003aa4828': { selectors: ['.pl-5, .px-5 (1085:1)'] },
+ e57ec4fe9e8ed36e38f1c50041fc9f47: { selectors: ['.m-auto (1089:1)'] },
+ f10380665932186d1effe0674a74ba12: { selectors: ['.mt-auto, .my-auto (1092:1)'] },
+ '2ce71a27023eb50a47c24a99399faa28': { selectors: ['.mr-auto, .mx-auto (1096:1)'] },
+ '196c77d357d314678cd3a99cfacbea96': { selectors: ['.mb-auto, .my-auto (1100:1)'] },
+ ca007ce268b463a6bf42145cf5ce3685: { selectors: ['.ml-auto, .mx-auto (1104:1)'] },
+ cb431b84034f2e710509c7656b3c6f16: { selectors: ['.text-monospace (1844:1)'] },
+ a8fc5ca823f51d72673577064387a029: { selectors: ['.text-justify (1847:1)'] },
+ '0bb94dfab7ca2c9892ebbd993b2baf0f': { selectors: ['.text-nowrap (1850:1)'] },
+ aea4958ce85ddc0cbffca1015c3a7eba: { selectors: ['.text-truncate (1853:1)'] },
+ '52b9443947b6b94a5c7e1b837da115e2': { selectors: ['.text-left (1858:1)'] },
+ baaf5136fc6e1c54ba29b6040f166d5f: { selectors: ['.text-right (1861:1)'] },
+ '282aa4319bee75af06cc2632b7124e26': { selectors: ['.text-center (1864:1)'] },
+ '1cb1c8ad9b560eca25ebcefe95c1b7fa': { selectors: ['.text-lowercase (1899:1)'] },
+ '45234533eac658ba2857e9c4d3bc78a5': { selectors: ['.text-uppercase (1902:1)'] },
+ f9e3f64237f2e81b6aed84223a0ceb1d: { selectors: ['.text-capitalize (1905:1)'] },
+ '09caca3d36aa9f3ef815e0da7e1a16b4': { selectors: ['.font-weight-light (1908:1)'] },
+ '25189f4fad18eaeef19e349c6680834c': { selectors: ['.font-weight-normal (1911:1)'] },
+ b2a9507678ec557603eb8ec077f0eb1f: { selectors: ['.font-weight-bold (1914:1)'] },
+ '7d2da06b621a98a8599e5ec82e39eac8': { selectors: ['.font-italic (1917:1)'] },
+ '0020d10e4fce033b418aace7c3143b82': { selectors: ['.text-white (1920:1)'] },
+ '34ad81e372a038e6f78ae4f22bd4813d': { selectors: ['.text-primary (1923:1)'] },
+ '9fde9a179d24755438ace2a874dda817': {
+ selectors: ['.text-secondary (1929:1)', '.text-muted (1974:1)'],
+ },
+ '9ffcb1532b3fb397c0e818850683da29': { selectors: ['.text-success (1935:1)'] },
+ f28fd089809bcd15d5684b158a0af98d: { selectors: ['.text-info (1941:1)'] },
+ '6cac1cb5ee5149e91e45d15d0bdae310': { selectors: ['.text-warning (1947:1)'] },
+ '2faab1e0abf22b20fdf05b9b01fff29b': { selectors: ['.text-danger (1953:1)'] },
+ '46b52fea531aaaf29b63c40be2356849': { selectors: ['.text-light (1959:1)'] },
+ '78f31d1ab6529decf28e0366a8ee81aa': { selectors: ['.text-dark (1965:1)'] },
+ '45330b41b77e8880ad7680c51e0f61c4': { selectors: ['.text-body (1971:1)'] },
+ '60d93588f62b5e85eb4f11dfd3461897': { selectors: ['.text-black-50 (1977:1)'] },
+ '7dea35658553032ff7b7cc0287613b7c': { selectors: ['.text-white-50 (1980:1)'] },
+ '61bf92980cac3d51d0cf1ba24c948fa1': { selectors: ['.text-hide (1983:1)'] },
+ '7dcad258820769677bc60871fafe9b93': { selectors: ['.visible (1990:1)'] },
+ '0f8833af4e2f4a6fc785bd7edc1e75b3': { selectors: ['.invisible (1993:1)'] },
+};
diff --git a/scripts/insert-rspec-profiling-data b/scripts/insert-rspec-profiling-data
index 10e337b9972..b34379764e0 100755
--- a/scripts/insert-rspec-profiling-data
+++ b/scripts/insert-rspec-profiling-data
@@ -22,7 +22,7 @@ module RspecProfiling
ENV['RSPEC_PROFILING_POSTGRES_URL']
end
- class Result < ActiveRecord::Base
+ class Result < ActiveRecord::Base # rubocop:disable Rails/ApplicationRecord
acts_as_copy_target
end
end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index 32fce946c17..e324f972640 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -1,12 +1,115 @@
[[ "$TRACE" ]] && set -x
export TILLER_NAMESPACE="$KUBE_NAMESPACE"
-function echoerr() { printf "\033[0;31m%s\n\033[0m" "$*" >&2; }
-function echoinfo() { printf "\033[0;33m%s\n\033[0m" "$*" >&2; }
+function echoerr() {
+ local header="${2}"
+
+ if [ -n "${header}" ]; then
+ printf "\n\033[0;31m** %s **\n\033[0m" "${1}" >&2;
+ else
+ printf "\033[0;31m%s\n\033[0m" "${1}" >&2;
+ fi
+}
+
+function echoinfo() {
+ local header="${2}"
+
+ if [ -n "${header}" ]; then
+ printf "\n\033[0;33m** %s **\n\033[0m" "${1}" >&2;
+ else
+ printf "\033[0;33m%s\n\033[0m" "${1}" >&2;
+ fi
+}
+
+function deployExists() {
+ local namespace="${1}"
+ local deploy="${2}"
+ echoinfo "Checking if ${deploy} exists in the ${namespace} namespace..." true
+
+ helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1
+ local deploy_exists=$?
+
+ echoinfo "Deployment status for ${deploy} is ${deploy_exists}"
+ return $deploy_exists
+}
+
+function previousDeployFailed() {
+ set +e
+ local deploy="${1}"
+ echoinfo "Checking for previous deployment of ${deploy}" true
+
+ helm status ${deploy} >/dev/null 2>&1
+ local status=$?
+
+ # if `status` is `0`, deployment exists, has a status
+ if [ $status -eq 0 ]; then
+ echoinfo "Previous deployment found, checking status..."
+ deployment_status=$(helm status ${deploy} | grep ^STATUS | cut -d' ' -f2)
+ echoinfo "Previous deployment state: ${deployment_status}"
+ if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then
+ status=0;
+ else
+ status=1;
+ fi
+ else
+ echoerr "Previous deployment NOT found."
+ fi
+ set -e
+ return $status
+}
+
+function delete() {
+ if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
+ echoerr "No release given, aborting the delete!"
+ return
+ fi
+
+ local track="${1-stable}"
+ local name="$CI_ENVIRONMENT_SLUG"
+
+ if [[ "$track" != "stable" ]]; then
+ name="$name-$track"
+ fi
+
+ echoinfo "Deleting release '$name'..." true
+
+ helm delete --purge "$name" || true
+}
+
+function cleanup() {
+ if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
+ echoerr "No release given, aborting the delete!"
+ return
+ fi
+
+ echoinfo "Cleaning up '$CI_ENVIRONMENT_SLUG'..." true
+
+ kubectl -n "$KUBE_NAMESPACE" delete \
+ ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \
+ -l release="$CI_ENVIRONMENT_SLUG" \
+ || true
+}
+
+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"
+ echoinfo "Running '${get_pod_cmd}'" true
+
+ while true; do
+ local pod_name="$(eval $get_pod_cmd)"
+ [[ "${pod_name}" == "" ]] || break
+
+ echoinfo "Waiting till '${app_name}' pod is ready";
+ sleep 5;
+ done
+
+ echoinfo "The pod name is '${pod_name}'."
+ echo "${pod_name}"
+}
function perform_review_app_deployment() {
check_kube_domain
- download_gitlab_chart
ensure_namespace
install_tiller
install_external_dns
@@ -15,6 +118,8 @@ function perform_review_app_deployment() {
}
function check_kube_domain() {
+ echoinfo "Checking that Kube domain exists..." true
+
if [ -z ${REVIEW_APPS_DOMAIN+x} ]; then
echo "In order to deploy or use Review Apps, REVIEW_APPS_DOMAIN variable must be set"
echo "You can do it in Auto DevOps project settings or defining a variable at group or project level"
@@ -25,36 +130,56 @@ function check_kube_domain() {
fi
}
-function download_gitlab_chart() {
- curl -o gitlab.tar.bz2 https://gitlab.com/charts/gitlab/-/archive/$GITLAB_HELM_CHART_REF/gitlab-$GITLAB_HELM_CHART_REF.tar.bz2
- tar -xjf gitlab.tar.bz2
- cd gitlab-$GITLAB_HELM_CHART_REF
-
- helm init --client-only
- helm repo add gitlab https://charts.gitlab.io
- helm dependency update
- helm dependency build
-}
-
function ensure_namespace() {
+ echoinfo "Ensuring the ${KUBE_NAMESPACE} namespace exists..." true
+
kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
}
function install_tiller() {
- echo "Checking Tiller..."
+ echoinfo "Checking deployment/tiller-deploy status in the ${TILLER_NAMESPACE} namespace..." true
+
+ echoinfo "Initiating the Helm client..."
+ helm init --client-only
+
helm init \
--upgrade \
--replicas 2
+
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
+
if ! helm version --debug; then
echo "Failed to init Tiller."
return 1
fi
- echo ""
+}
+
+function install_external_dns() {
+ local release_name="dns-gitlab-review-app"
+ local domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}')
+ echoinfo "Installing external DNS for domain ${domain}..." true
+
+ if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then
+ echoinfo "Installing external-dns Helm chart"
+ helm repo update
+ helm install stable/external-dns \
+ -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.zoneType="public" \
+ --set domainFilters[0]="${domain}" \
+ --set txtOwnerId="${KUBE_NAMESPACE}" \
+ --set rbac.create="true" \
+ --set policy="sync"
+ else
+ echoinfo "The external-dns Helm chart is already successfully deployed."
+ fi
}
function create_secret() {
- echo "Create secret..."
+ echoinfo "Creating the ${CI_ENVIRONMENT_SLUG}-gitlab-initial-root-password secret in the ${KUBE_NAMESPACE} namespace..." true
kubectl create secret generic -n "$KUBE_NAMESPACE" \
$CI_ENVIRONMENT_SLUG-gitlab-initial-root-password \
@@ -62,43 +187,28 @@ function create_secret() {
--dry-run -o json | kubectl apply -f -
}
-function deployExists() {
- local namespace="${1}"
- local deploy="${2}"
- helm status --tiller-namespace "${namespace}" "${deploy}" >/dev/null 2>&1
- return $?
-}
+function download_gitlab_chart() {
+ echoinfo "Downloading the GitLab chart..." true
-function previousDeployFailed() {
- set +e
- deploy="${1}"
- echo "Checking for previous deployment of ${deploy}"
- deployment_status=$(helm status ${deploy} >/dev/null 2>&1)
- status=$?
- # if `status` is `0`, deployment exists, has a status
- if [ $status -eq 0 ]; then
- echo "Previous deployment found, checking status"
- deployment_status=$(helm status ${deploy} | grep ^STATUS | cut -d' ' -f2)
- echo "Previous deployment state: $deployment_status"
- if [[ "$deployment_status" == "FAILED" || "$deployment_status" == "PENDING_UPGRADE" || "$deployment_status" == "PENDING_INSTALL" ]]; then
- status=0;
- else
- status=1;
- fi
- else
- echo "Previous deployment NOT found."
- fi
- set -e
- return $status
+ curl -o gitlab.tar.bz2 https://gitlab.com/charts/gitlab/-/archive/$GITLAB_HELM_CHART_REF/gitlab-$GITLAB_HELM_CHART_REF.tar.bz2
+ tar -xjf gitlab.tar.bz2
+ cd gitlab-$GITLAB_HELM_CHART_REF
+
+ echoinfo "Adding the gitlab repo to Helm..."
+ helm repo add gitlab https://charts.gitlab.io
+
+ echoinfo "Building the gitlab chart's dependencies..."
+ helm dependency build .
}
function deploy() {
- track="${1-stable}"
- name="$CI_ENVIRONMENT_SLUG"
+ local track="${1-stable}"
+ local name="$CI_ENVIRONMENT_SLUG"
if [[ "$track" != "stable" ]]; then
name="$name-$track"
fi
+ echoinfo "Deploying ${name}..." true
replicas="1"
service_enabled="false"
@@ -140,9 +250,7 @@ function deploy() {
fi
create_secret
-
- helm repo add gitlab https://charts.gitlab.io/
- helm dep update .
+ download_gitlab_chart
HELM_CMD=$(cat << EOF
helm upgrade --install \
@@ -188,92 +296,20 @@ HELM_CMD=$(cat << EOF
EOF
)
- echo "Deploying with:"
- echo $HELM_CMD
+ echoinfo "Deploying with:"
+ echoinfo "${HELM_CMD}"
eval $HELM_CMD
}
-function delete() {
- track="${1-stable}"
- name="$CI_ENVIRONMENT_SLUG"
-
- if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
- echo "No release given, aborting the delete!"
- return
- fi
-
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
-
- if ! deployExists "${KUBE_NAMESPACE}" "${name}"; then
- echo "The release $name doesn't exist, aborting the cleanup!"
- return
- fi
-
- echo "Deleting release '$name'..."
- helm delete --purge "$name" || true
-}
-
-function cleanup() {
- if [ -z "$CI_ENVIRONMENT_SLUG" ]; then
- echo "No release given, aborting the delete!"
- return
- fi
-
- echo "Cleaning up '$CI_ENVIRONMENT_SLUG'..."
- kubectl -n "$KUBE_NAMESPACE" delete \
- ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \
- -l release="$CI_ENVIRONMENT_SLUG" \
- || true
-}
-
-function install_external_dns() {
- local release_name="dns-gitlab-review-app"
- local domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}')
-
- if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then
- echo "Installing external-dns helm chart"
- helm repo update
- helm install stable/external-dns \
- -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.zoneType="public" \
- --set domainFilters[0]="${domain}" \
- --set txtOwnerId="${KUBE_NAMESPACE}" \
- --set rbac.create="true" \
- --set policy="sync"
- fi
-}
-
-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"
- echoinfo "Running '${get_pod_cmd}'"
-
- while true; do
- local pod_name="$(eval $get_pod_cmd)"
- [[ "${pod_name}" == "" ]] || break
-
- echoinfo "Waiting till '${app_name}' pod is ready";
- sleep 5;
- done
-
- echoinfo "The pod name is '${pod_name}'."
- echo "${pod_name}"
-}
-
function add_license() {
if [ -z "${REVIEW_APPS_EE_LICENSE}" ]; then echo "License not found" && return; fi
task_runner_pod=$(get_pod "task-runner");
if [ -z "${task_runner_pod}" ]; then echo "Task runner pod not found" && return; fi
+ echoinfo "Installing license..." true
+
echo "${REVIEW_APPS_EE_LICENSE}" > /tmp/license.gitlab
kubectl -n "$KUBE_NAMESPACE" cp /tmp/license.gitlab ${task_runner_pod}:/tmp/license.gitlab
rm /tmp/license.gitlab
diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
new file mode 100644
index 00000000000..c19a752b07b
--- /dev/null
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GraphqlController do
+ before do
+ stub_feature_flags(graphql: true)
+ end
+
+ describe 'POST #execute' do
+ context 'when user is logged in' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'returns 200 when user can access API' do
+ post :execute
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'returns access denied template when user cannot access API' do
+ # User cannot access API in a couple of cases
+ # * When user is internal(like ghost users)
+ # * When user is blocked
+ expect(Ability).to receive(:allowed?).with(user, :access_api, :global).and_return(false)
+
+ post :execute
+
+ expect(response.status).to eq(403)
+ expect(response).to render_template('errors/access_denied')
+ end
+ end
+
+ context 'when user is not logged in' do
+ it 'returns 200' do
+ post :execute
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 79f97aa4170..c8fa93a74ee 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -86,6 +86,10 @@ describe Projects::MergeRequestsController do
end
describe 'as json' do
+ before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ end
+
context 'with basic serializer param' do
it 'renders basic MR entity as json' do
go(serializer: 'basic', format: :json)
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index 1cc3c0e03d8..97405ec7c58 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -3,6 +3,7 @@ FactoryBot.define do
user
name 'test-cluster'
cluster_type :project_type
+ managed true
trait :instance do
cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
@@ -63,5 +64,9 @@ FactoryBot.define do
trait :with_domain do
domain 'example.com'
end
+
+ trait :user_managed do
+ managed false
+ end
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index a73f330a7a9..abf0e6bccb7 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -46,10 +46,26 @@ FactoryBot.define do
target_branch "improve/awesome"
end
+ trait :merged_last_month do
+ merged
+
+ after(:build) do |merge_request|
+ merge_request.build_metrics.merged_at = 1.month.ago
+ end
+ end
+
trait :closed do
state :closed
end
+ trait :closed_last_month do
+ closed
+
+ after(:build) do |merge_request|
+ merge_request.build_metrics.latest_closed_at = 1.month.ago
+ end
+ end
+
trait :opened do
state :opened
end
@@ -101,9 +117,20 @@ FactoryBot.define do
end
end
+ trait :with_legacy_detached_merge_request_pipeline do
+ after(:create) do |merge_request|
+ merge_request.merge_request_pipelines << create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.source_branch_sha)
+ end
+ end
+
trait :with_detached_merge_request_pipeline do
- after(:build) do |merge_request|
- merge_request.merge_request_pipelines << build(:ci_pipeline,
+ after(:create) do |merge_request|
+ merge_request.merge_request_pipelines << create(:ci_pipeline,
source: :merge_request_event,
merge_request: merge_request,
project: merge_request.source_project,
@@ -119,7 +146,7 @@ FactoryBot.define do
target_sha { target_branch_sha }
end
- after(:build) do |merge_request, evaluator|
+ after(:create) do |merge_request, evaluator|
merge_request.merge_request_pipelines << create(:ci_pipeline,
source: :merge_request_event,
merge_request: merge_request,
diff --git a/spec/factories/suggestions.rb b/spec/factories/suggestions.rb
index 307523cc061..b1427e0211f 100644
--- a/spec/factories/suggestions.rb
+++ b/spec/factories/suggestions.rb
@@ -16,5 +16,11 @@ FactoryBot.define do
applied true
commit_id { RepoHelpers.sample_commit.id }
end
+
+ trait :content_from_repo do
+ after(:build) do |suggestion, evaluator|
+ suggestion.from_content = suggestion.fetch_from_content
+ end
+ end
end
end
diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb
index b9fc52d0dce..d2e46d15730 100644
--- a/spec/features/clusters/cluster_detail_page_spec.rb
+++ b/spec/features/clusters/cluster_detail_page_spec.rb
@@ -53,12 +53,80 @@ describe 'Clusterable > Show page' do
end
end
+ shared_examples 'editing a GCP cluster' do
+ before do
+ clusterable.add_maintainer(current_user)
+ visit cluster_path
+ end
+
+ it 'is not able to edit the name, API url, CA certificate nor token' do
+ within('#js-cluster-details') do
+ cluster_name_field = find('.cluster-name')
+ api_url_field = find('#cluster_platform_kubernetes_attributes_api_url')
+ ca_certificate_field = find('#cluster_platform_kubernetes_attributes_ca_cert')
+ token_field = find('#cluster_platform_kubernetes_attributes_token')
+
+ expect(cluster_name_field).to be_readonly
+ expect(api_url_field).to be_readonly
+ expect(ca_certificate_field).to be_readonly
+ expect(token_field).to be_readonly
+ end
+ end
+
+ it 'displays GKE information' do
+ within('#advanced-settings-section') do
+ expect(page).to have_content('Google Kubernetes Engine')
+ expect(page).to have_content('Manage your Kubernetes cluster by visiting')
+ end
+ end
+ end
+
+ shared_examples 'editing a user-provided cluster' do
+ before do
+ clusterable.add_maintainer(current_user)
+ visit cluster_path
+ end
+
+ it 'is able to edit the name, API url, CA certificate and token' do
+ within('#js-cluster-details') do
+ cluster_name_field = find('#cluster_name')
+ api_url_field = find('#cluster_platform_kubernetes_attributes_api_url')
+ ca_certificate_field = find('#cluster_platform_kubernetes_attributes_ca_cert')
+ token_field = find('#cluster_platform_kubernetes_attributes_token')
+
+ expect(cluster_name_field).not_to be_readonly
+ expect(api_url_field).not_to be_readonly
+ expect(ca_certificate_field).not_to be_readonly
+ expect(token_field).not_to be_readonly
+ end
+ end
+
+ it 'does not display GKE information' do
+ within('#advanced-settings-section') do
+ expect(page).not_to have_content('Google Kubernetes Engine')
+ expect(page).not_to have_content('Manage your Kubernetes cluster by visiting')
+ end
+ end
+ end
+
context 'when clusterable is a project' do
it_behaves_like 'editing domain' do
let(:clusterable) { create(:project) }
let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
let(:cluster_path) { project_cluster_path(clusterable, cluster) }
end
+
+ it_behaves_like 'editing a GCP cluster' do
+ let(:clusterable) { create(:project) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
+ let(:cluster_path) { project_cluster_path(clusterable, cluster) }
+ end
+
+ it_behaves_like 'editing a user-provided cluster' do
+ let(:clusterable) { create(:project) }
+ let(:cluster) { create(:cluster, :provided_by_user, :project, projects: [clusterable]) }
+ let(:cluster_path) { project_cluster_path(clusterable, cluster) }
+ end
end
context 'when clusterable is a group' do
@@ -67,5 +135,17 @@ describe 'Clusterable > Show page' do
let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
let(:cluster_path) { group_cluster_path(clusterable, cluster) }
end
+
+ it_behaves_like 'editing a GCP cluster' do
+ let(:clusterable) { create(:group) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
+ let(:cluster_path) { group_cluster_path(clusterable, cluster) }
+ end
+
+ it_behaves_like 'editing a user-provided cluster' do
+ let(:clusterable) { create(:group) }
+ let(:cluster) { create(:cluster, :provided_by_user, :group, groups: [clusterable]) }
+ let(:cluster_path) { group_cluster_path(clusterable, cluster) }
+ end
end
end
diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb
index b5e7c3954e2..362f8a468ec 100644
--- a/spec/features/issues/user_uses_quick_actions_spec.rb
+++ b/spec/features/issues/user_uses_quick_actions_spec.rb
@@ -3,8 +3,41 @@ require 'rails_helper'
describe 'Issues > User uses quick actions', :js do
include Spec::Support::Helpers::Features::NotesHelpers
- it_behaves_like 'issuable record that supports quick actions in its description and notes', :issue do
+ context "issuable common quick actions" do
+ let(:new_url_opts) { {} }
+ let(:maintainer) { create(:user) }
+ let(:project) { create(:project, :public) }
+ let!(:label_bug) { create(:label, project: project, title: 'bug') }
+ let!(:label_feature) { create(:label, project: project, title: 'feature') }
+ let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let(:issuable) { create(:issue, project: project) }
+ let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])}
+
+ it_behaves_like 'assign quick action', :issue
+ it_behaves_like 'unassign quick action', :issue
+ it_behaves_like 'close quick action', :issue
+ it_behaves_like 'reopen quick action', :issue
+ it_behaves_like 'title quick action', :issue
+ it_behaves_like 'todo quick action', :issue
+ it_behaves_like 'done quick action', :issue
+ it_behaves_like 'subscribe quick action', :issue
+ it_behaves_like 'unsubscribe quick action', :issue
+ it_behaves_like 'lock quick action', :issue
+ it_behaves_like 'unlock quick action', :issue
+ it_behaves_like 'milestone quick action', :issue
+ it_behaves_like 'remove_milestone quick action', :issue
+ it_behaves_like 'label quick action', :issue
+ it_behaves_like 'unlabel quick action', :issue
+ it_behaves_like 'relabel quick action', :issue
+ it_behaves_like 'award quick action', :issue
+ it_behaves_like 'estimate quick action', :issue
+ it_behaves_like 'remove_estimate quick action', :issue
+ it_behaves_like 'spend quick action', :issue
+ it_behaves_like 'remove_time_spent quick action', :issue
+ it_behaves_like 'shrug quick action', :issue
+ it_behaves_like 'tableflip quick action', :issue
+ it_behaves_like 'copy_metadata quick action', :issue
+ it_behaves_like 'issuable time tracker', :issue
end
describe 'issue-only commands' do
@@ -15,37 +48,17 @@ describe 'Issues > User uses quick actions', :js do
project.add_maintainer(user)
sign_in(user)
visit project_issue_path(project, issue)
+ wait_for_all_requests
end
after do
wait_for_requests
end
- describe 'time tracking' do
- let(:issue) { create(:issue, project: project) }
-
- before do
- visit project_issue_path(project, issue)
- end
-
- it_behaves_like 'issuable time tracker'
- end
-
describe 'adding a due date from note' do
let(:issue) { create(:issue, project: project) }
- context 'when the current user can update the due date' do
- it 'does not create a note, and sets the due date accordingly' do
- add_note("/due 2016-08-28")
-
- expect(page).not_to have_content '/due 2016-08-28'
- expect(page).to have_content 'Commands applied'
-
- issue.reload
-
- expect(issue.due_date).to eq Date.new(2016, 8, 28)
- end
- end
+ it_behaves_like 'due quick action available and date can be added'
context 'when the current user cannot update the due date' do
let(:guest) { create(:user) }
@@ -56,35 +69,14 @@ describe 'Issues > User uses quick actions', :js do
visit project_issue_path(project, issue)
end
- it 'does not create a note, and sets the due date accordingly' do
- add_note("/due 2016-08-28")
-
- expect(page).not_to have_content 'Commands applied'
-
- issue.reload
-
- expect(issue.due_date).to be_nil
- end
+ it_behaves_like 'due quick action not available'
end
end
describe 'removing a due date from note' do
let(:issue) { create(:issue, project: project, due_date: Date.new(2016, 8, 28)) }
- context 'when the current user can update the due date' do
- it 'does not create a note, and removes the due date accordingly' do
- expect(issue.due_date).to eq Date.new(2016, 8, 28)
-
- add_note("/remove_due_date")
-
- expect(page).not_to have_content '/remove_due_date'
- expect(page).to have_content 'Commands applied'
-
- issue.reload
-
- expect(issue.due_date).to be_nil
- end
- end
+ it_behaves_like 'remove_due_date action available and due date can be removed'
context 'when the current user cannot update the due date' do
let(:guest) { create(:user) }
@@ -95,15 +87,7 @@ describe 'Issues > User uses quick actions', :js do
visit project_issue_path(project, issue)
end
- it 'does not create a note, and sets the due date accordingly' do
- add_note("/remove_due_date")
-
- expect(page).not_to have_content 'Commands applied'
-
- issue.reload
-
- expect(issue.due_date).to eq Date.new(2016, 8, 28)
- end
+ it_behaves_like 'remove_due_date action not available'
end
end
@@ -200,6 +184,7 @@ describe 'Issues > User uses quick actions', :js do
gitlab_sign_out
sign_in(user)
visit project_issue_path(project, issue)
+ wait_for_requests
end
it 'moves the issue' do
@@ -221,6 +206,7 @@ describe 'Issues > User uses quick actions', :js do
gitlab_sign_out
sign_in(user)
visit project_issue_path(project, issue)
+ wait_for_requests
end
it 'does not move the issue' do
@@ -238,6 +224,7 @@ describe 'Issues > User uses quick actions', :js do
gitlab_sign_out
sign_in(user)
visit project_issue_path(project, issue)
+ wait_for_requests
end
it 'does not move the issue' do
diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
index 8c2599615cb..2f7d359575e 100644
--- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -5,9 +5,7 @@ describe 'Merge request > User scrolls to note on load', :js do
let(:user) { project.creator }
let(:merge_request) { create(:merge_request, source_project: project, author: user) }
let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
- let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) }
let(:fragment_id) { "#note_#{note.id}" }
- let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" }
before do
sign_in(user)
@@ -45,13 +43,35 @@ describe 'Merge request > User scrolls to note on load', :js do
end
end
- # TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
- xit 'expands collapsed notes' do
- visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
- note_element = find(collapsed_fragment_id)
- note_container = note_element.ancestor('.timeline-content')
+ context 'resolved notes' do
+ let(:collapsed_fragment_id) { "#note_#{resolved_note.id}" }
- expect(note_element.visible?).to eq true
- expect(note_container.find('.line_content.noteable_line.old', match: :first).visible?).to eq true
+ context 'when diff note' do
+ let(:resolved_note) { create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: project) }
+
+ it 'expands collapsed notes' do
+ visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
+
+ note_element = find(collapsed_fragment_id)
+ diff_container = note_element.ancestor('.diff-content')
+
+ expect(note_element.visible?).to eq(true)
+ expect(diff_container.visible?).to eq(true)
+ end
+ end
+
+ context 'when non-diff note' do
+ let(:non_diff_discussion) { create(:discussion_note_on_merge_request, :resolved, noteable: merge_request, project: project) }
+ let(:resolved_note) { create(:discussion_note_on_merge_request, :resolved, noteable: merge_request, project: project, in_reply_to: non_diff_discussion) }
+
+ it 'expands collapsed replies' do
+ visit "#{project_merge_request_path(project, merge_request)}#{collapsed_fragment_id}"
+
+ note_element = find(collapsed_fragment_id)
+
+ expect(note_element.visible?).to eq(true)
+ expect(note_element.sibling('.replies-toggle')[:class]).to include('expanded')
+ end
+ end
end
end
diff --git a/spec/features/merge_request/user_uses_quick_actions_spec.rb b/spec/features/merge_request/user_uses_quick_actions_spec.rb
index b81478a481f..a2b5859bd1e 100644
--- a/spec/features/merge_request/user_uses_quick_actions_spec.rb
+++ b/spec/features/merge_request/user_uses_quick_actions_spec.rb
@@ -9,9 +9,41 @@ describe 'Merge request > User uses quick actions', :js do
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
- it_behaves_like 'issuable record that supports quick actions in its description and notes', :merge_request do
+ context "issuable common quick actions" do
+ let!(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
+ let(:maintainer) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+ let!(:label_bug) { create(:label, project: project, title: 'bug') }
+ let!(:label_feature) { create(:label, project: project, title: 'feature') }
+ let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
let(:issuable) { create(:merge_request, source_project: project) }
- let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } }
+ let(:source_issuable) { create(:issue, project: project, milestone: milestone, labels: [label_bug, label_feature])}
+
+ it_behaves_like 'assign quick action', :merge_request
+ it_behaves_like 'unassign quick action', :merge_request
+ it_behaves_like 'close quick action', :merge_request
+ it_behaves_like 'reopen quick action', :merge_request
+ it_behaves_like 'title quick action', :merge_request
+ it_behaves_like 'todo quick action', :merge_request
+ it_behaves_like 'done quick action', :merge_request
+ it_behaves_like 'subscribe quick action', :merge_request
+ it_behaves_like 'unsubscribe quick action', :merge_request
+ it_behaves_like 'lock quick action', :merge_request
+ it_behaves_like 'unlock quick action', :merge_request
+ it_behaves_like 'milestone quick action', :merge_request
+ it_behaves_like 'remove_milestone quick action', :merge_request
+ it_behaves_like 'label quick action', :merge_request
+ it_behaves_like 'unlabel quick action', :merge_request
+ it_behaves_like 'relabel quick action', :merge_request
+ it_behaves_like 'award quick action', :merge_request
+ it_behaves_like 'estimate quick action', :merge_request
+ it_behaves_like 'remove_estimate quick action', :merge_request
+ it_behaves_like 'spend quick action', :merge_request
+ it_behaves_like 'remove_time_spent quick action', :merge_request
+ it_behaves_like 'shrug quick action', :merge_request
+ it_behaves_like 'tableflip quick action', :merge_request
+ it_behaves_like 'copy_metadata quick action', :merge_request
+ it_behaves_like 'issuable time tracker', :merge_request
end
describe 'merge-request-only commands' do
@@ -24,20 +56,12 @@ describe 'Merge request > User uses quick actions', :js do
project.add_maintainer(user)
end
- describe 'time tracking' do
- before do
- sign_in(user)
- visit project_merge_request_path(project, merge_request)
- end
-
- it_behaves_like 'issuable time tracker'
- end
-
describe 'toggling the WIP prefix in the title from note' do
context 'when the current user can toggle the WIP prefix' do
before do
sign_in(user)
visit project_merge_request_path(project, merge_request)
+ wait_for_requests
end
it 'adds the WIP: prefix to the title' do
@@ -135,11 +159,16 @@ describe 'Merge request > User uses quick actions', :js do
visit project_merge_request_path(project, merge_request)
end
- it 'does not recognize the command nor create a note' do
- add_note('/due 2016-08-28')
+ it_behaves_like 'due quick action not available'
+ end
- expect(page).not_to have_content '/due 2016-08-28'
+ describe 'removing a due date from note' do
+ before do
+ sign_in(user)
+ visit project_merge_request_path(project, merge_request)
end
+
+ it_behaves_like 'remove_due_date action not available'
end
describe '/target_branch command in merge request' do
diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb
index 71022c6bb08..849fab62fc6 100644
--- a/spec/features/merge_request/user_views_open_merge_request_spec.rb
+++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb
@@ -13,7 +13,7 @@ describe 'User views an open merge request' do
end
it 'renders both the title and the description' do
- node = find('.wiki h1 a#user-content-description-header')
+ node = find('.md h1 a#user-content-description-header')
expect(node[:href]).to end_with('#description-header')
# Work around a weird Capybara behavior where calling `parent` on a node
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index 6e349395017..adac59b89ef 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -122,4 +122,32 @@ describe 'Milestone' do
expect(page).to have_selector('.popover')
end
end
+
+ describe 'reopen closed milestones' do
+ before do
+ create(:milestone, :closed, project: project)
+ end
+
+ describe 'group milestones page' do
+ it 'reopens the milestone' do
+ visit group_milestones_path(group, { state: 'closed' })
+
+ click_link 'Reopen Milestone'
+
+ expect(page).not_to have_selector('.status-box-closed')
+ expect(page).to have_selector('.status-box-open')
+ end
+ end
+
+ describe 'project milestones page' do
+ it 'reopens the milestone' do
+ visit project_milestones_path(project, { state: 'closed' })
+
+ click_link 'Reopen Milestone'
+
+ expect(page).not_to have_selector('.status-box-closed')
+ expect(page).to have_selector('.status-box-open')
+ end
+ end
+ end
end
diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
index 5f630c9ffa4..a1fcd4024c0 100644
--- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
@@ -19,6 +19,12 @@ describe "User browses artifacts" do
visit(browse_project_job_artifacts_path(project, job))
end
+ it "renders a link to the job in the breadcrumbs" do
+ page.within('.js-breadcrumbs-list') do
+ expect(page).to have_link("##{job.id}", href: project_job_path(project, job))
+ end
+ end
+
it "shows artifacts" do
expect(page).not_to have_selector(".build-sidebar")
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 3090f1a2131..fe71cb7661a 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -319,7 +319,7 @@ describe 'Environment' do
yield
- GitPushService.new(project, user, params).execute
+ Git::BranchPushService.new(project, user, params).execute
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 9fdf78baa1e..b197557039d 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -59,11 +59,11 @@ describe 'Pipeline', :js do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project, ref: 'master', sha: project.commit.id, user: user) }
- before do
- visit project_pipeline_path(project, pipeline)
- end
+ subject(:visit_pipeline) { visit project_pipeline_path(project, pipeline) }
it 'shows the pipeline graph' do
+ visit_pipeline
+
expect(page).to have_selector('.pipeline-visualization')
expect(page).to have_content('Build')
expect(page).to have_content('Test')
@@ -73,14 +73,20 @@ describe 'Pipeline', :js do
end
it 'shows Pipeline tab pane as active' do
+ visit_pipeline
+
expect(page).to have_css('#js-tab-pipeline.active')
end
it 'shows link to the pipeline ref' do
+ visit_pipeline
+
expect(page).to have_link(pipeline.ref)
end
it 'shows the pipeline information' do
+ visit_pipeline
+
within '.pipeline-info' do
expect(page).to have_content("#{pipeline.statuses.count} jobs " \
"for #{pipeline.ref} ")
@@ -96,6 +102,10 @@ describe 'Pipeline', :js do
end
describe 'pipeline graph' do
+ before do
+ visit_pipeline
+ end
+
context 'when pipeline has running builds' do
it 'shows a running icon and a cancel action for the running build' do
page.within('#ci-badge-deploy') do
@@ -227,6 +237,10 @@ describe 'Pipeline', :js do
end
context 'page tabs' do
+ before do
+ visit_pipeline
+ end
+
it 'shows Pipeline, Jobs and Failed Jobs tabs with link' do
expect(page).to have_link('Pipeline')
expect(page).to have_link('Jobs')
@@ -253,6 +267,10 @@ describe 'Pipeline', :js do
end
context 'retrying jobs' do
+ before do
+ visit_pipeline
+ end
+
it { expect(page).not_to have_content('retried') }
context 'when retrying' do
@@ -265,6 +283,10 @@ describe 'Pipeline', :js do
end
context 'canceling jobs' do
+ before do
+ visit_pipeline
+ end
+
it { expect(page).not_to have_selector('.ci-canceled') }
context 'when canceling' do
@@ -284,6 +306,10 @@ describe 'Pipeline', :js do
user: user)
end
+ before do
+ visit_pipeline
+ end
+
it 'does not render link to the pipeline ref' do
expect(page).not_to have_link(pipeline.ref)
expect(page).to have_content(pipeline.ref)
@@ -305,6 +331,10 @@ describe 'Pipeline', :js do
merge_request.all_pipelines.last
end
+ before do
+ visit_pipeline
+ end
+
it 'shows the pipeline information' do
within '.pipeline-info' do
expect(page).to have_content("#{pipeline.statuses.count} jobs " \
@@ -356,6 +386,8 @@ describe 'Pipeline', :js do
before do
pipeline.update(user: user)
+
+ visit_pipeline
end
it 'shows the pipeline information' do
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index b1a7f167977..efb7b01f5ad 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -136,7 +136,7 @@ describe "User creates wiki page" do
click_button("Create page")
end
- page.within ".wiki" do
+ page.within ".md" do
expect(page).to have_selector(".katex", count: 3).and have_content("2+2 is 4")
end
end
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index eeacaf5f72a..fc6726985ae 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -70,7 +70,7 @@ describe 'Comments on personal snippets', :js do
fill_in 'note[note]', with: 'This is **awesome**!'
find('.js-md-preview-button').click
- page.within('.new-note .md-preview') do
+ page.within('.new-note .md-preview-holder') do
expect(page).to have_content('This is awesome!')
expect(page).to have_selector('strong')
end
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index 879c46d7c4e..1c97d5ec5b4 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -37,7 +37,7 @@ describe 'User creates snippet', :js do
dropzone_file Rails.root.join('spec', 'fixtures', 'banana_sample.gif')
find('.js-md-preview-button').click
- page.within('#new_personal_snippet .md-preview') do
+ page.within('#new_personal_snippet .md-preview-holder') do
expect(page).to have_content('My Snippet')
link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
index 8d567e925ef..bdbbe645779 100644
--- a/spec/features/tags/master_deletes_tag_spec.rb
+++ b/spec/features/tags/master_deletes_tag_spec.rb
@@ -37,7 +37,7 @@ describe 'Maintainer deletes tag' do
context 'when pre-receive hook fails', :js do
before do
allow_any_instance_of(Gitlab::GitalyClient::OperationService).to receive(:rm_tag)
- .and_raise(Gitlab::Git::PreReceiveError, 'Do not delete tags')
+ .and_raise(Gitlab::Git::PreReceiveError, 'GitLab: Do not delete tags')
end
it 'shows the error message' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 6fe840dccf6..33d9c10f5e8 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -79,7 +79,7 @@ describe 'Task Lists' do
visit_issue(project, issue)
wait_for_requests
- expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector('a.btn-close')
end
@@ -87,14 +87,14 @@ describe 'Task Lists' do
visit_issue(project, issue)
wait_for_requests
- expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
logout(:user)
login_as(user2)
visit current_path
wait_for_requests
- expect(page).to have_selector(".wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector(".md .task-list .task-list-item .task-list-item-checkbox")
end
it 'provides a summary on Issues#index' do
@@ -231,7 +231,7 @@ describe 'Task Lists' do
container = '.detail-page-description .description.js-task-list-container'
expect(page).to have_selector(container)
- expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
+ expect(page).to have_selector("#{container} .md .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close')
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 368a814874f..9d5780d29b0 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -447,7 +447,7 @@ describe 'Login' do
'You can leave Group 1 and leave Group 2. '\
'You need to do this '\
'before '\
- "#{(Time.zone.now + 2.days).strftime("%a, %-d %b %Y %H:%M:%S %z")}"
+ "#{(Time.zone.now + 2.days).strftime("%a, %d %b %Y %H:%M:%S %z")}"
)
end
end
diff --git a/spec/frontend/.eslintrc.yml b/spec/frontend/.eslintrc.yml
index 046215e4c93..054dc27cda6 100644
--- a/spec/frontend/.eslintrc.yml
+++ b/spec/frontend/.eslintrc.yml
@@ -2,8 +2,13 @@
env:
jest/globals: true
plugins:
-- jest
+ - jest
settings:
import/resolver:
jest:
- jestConfigFile: "jest.config.js"
+ jestConfigFile: 'jest.config.js'
+globals:
+ getJSONFixture: false
+ loadFixtures: false
+ preloadFixtures: false
+ setFixtures: false
diff --git a/spec/frontend/environment.js b/spec/frontend/environment.js
new file mode 100644
index 00000000000..34df8019a2e
--- /dev/null
+++ b/spec/frontend/environment.js
@@ -0,0 +1,52 @@
+/* eslint-disable import/no-commonjs */
+
+const { ErrorWithStack } = require('jest-util');
+const JSDOMEnvironment = require('jest-environment-jsdom');
+
+class CustomEnvironment extends JSDOMEnvironment {
+ constructor(config, context) {
+ super(config, context);
+
+ Object.assign(context.console, {
+ error(...args) {
+ throw new ErrorWithStack(
+ `Unexpected call of console.error() with:\n\n${args.join(', ')}`,
+ this.error,
+ );
+ },
+
+ warn(...args) {
+ throw new ErrorWithStack(
+ `Unexpected call of console.warn() with:\n\n${args.join(', ')}`,
+ this.warn,
+ );
+ },
+ });
+
+ const { testEnvironmentOptions } = config;
+ this.global.gon = {
+ ee: testEnvironmentOptions.IS_EE,
+ };
+
+ this.rejectedPromises = [];
+
+ this.global.promiseRejectionHandler = error => {
+ this.rejectedPromises.push(error);
+ };
+ }
+
+ async teardown() {
+ await new Promise(setImmediate);
+
+ if (this.rejectedPromises.length > 0) {
+ throw new ErrorWithStack(
+ `Unhandled Promise rejections: ${this.rejectedPromises.join(', ')}`,
+ this.teardown,
+ );
+ }
+
+ await super.teardown();
+ }
+}
+
+module.exports = CustomEnvironment;
diff --git a/spec/frontend/helpers/class_spec_helper.js b/spec/frontend/helpers/class_spec_helper.js
new file mode 100644
index 00000000000..7a60d33b471
--- /dev/null
+++ b/spec/frontend/helpers/class_spec_helper.js
@@ -0,0 +1,9 @@
+export default class ClassSpecHelper {
+ static itShouldBeAStaticMethod(base, method) {
+ return it('should be a static method', () => {
+ expect(Object.prototype.hasOwnProperty.call(base, method)).toBeTruthy();
+ });
+ }
+}
+
+window.ClassSpecHelper = ClassSpecHelper;
diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js
index f96f27c4d80..f0351aa31c6 100644
--- a/spec/frontend/helpers/fixtures.js
+++ b/spec/frontend/helpers/fixtures.js
@@ -1,24 +1,36 @@
-/* eslint-disable import/prefer-default-export, global-require, import/no-dynamic-require */
-
import fs from 'fs';
import path from 'path';
-// jest-util is part of Jest
-// eslint-disable-next-line import/no-extraneous-dependencies
import { ErrorWithStack } from 'jest-util';
const fixturesBasePath = path.join(process.cwd(), 'spec', 'javascripts', 'fixtures');
-export function getJSONFixture(relativePath, ee = false) {
- const absolutePath = path.join(fixturesBasePath, ee ? 'ee' : '', relativePath);
+export function getFixture(relativePath) {
+ const absolutePath = path.join(fixturesBasePath, relativePath);
if (!fs.existsSync(absolutePath)) {
throw new ErrorWithStack(
`Fixture file ${relativePath} does not exist.
Did you run bin/rake karma:fixtures?`,
- getJSONFixture,
+ getFixture,
);
}
- return require(absolutePath);
+ return fs.readFileSync(absolutePath, 'utf8');
}
+
+export const getJSONFixture = relativePath => JSON.parse(getFixture(relativePath));
+
+export const resetHTMLFixture = () => {
+ document.body.textContent = '';
+};
+
+export const setHTMLFixture = (htmlContent, resetHook = afterEach) => {
+ document.body.outerHTML = htmlContent;
+ resetHook(resetHTMLFixture);
+};
+
+export const loadHTMLFixture = (relativePath, resetHook = afterEach) => {
+ const fileContent = getFixture(relativePath);
+ setHTMLFixture(fileContent, resetHook);
+};
diff --git a/spec/frontend/helpers/locale_helper.js b/spec/frontend/helpers/locale_helper.js
new file mode 100644
index 00000000000..80047b06003
--- /dev/null
+++ b/spec/frontend/helpers/locale_helper.js
@@ -0,0 +1,11 @@
+/* eslint-disable import/prefer-default-export */
+
+export const setLanguage = languageCode => {
+ const htmlElement = document.querySelector('html');
+
+ if (languageCode) {
+ htmlElement.setAttribute('lang', languageCode);
+ } else {
+ htmlElement.removeAttribute('lang');
+ }
+};
diff --git a/spec/frontend/helpers/scroll_into_view_promise.js b/spec/frontend/helpers/scroll_into_view_promise.js
new file mode 100644
index 00000000000..0edea2103da
--- /dev/null
+++ b/spec/frontend/helpers/scroll_into_view_promise.js
@@ -0,0 +1,28 @@
+export default function scrollIntoViewPromise(intersectionTarget, timeout = 100, maxTries = 5) {
+ return new Promise((resolve, reject) => {
+ let intersectionObserver;
+ let retry = 0;
+
+ const intervalId = setInterval(() => {
+ if (retry >= maxTries) {
+ intersectionObserver.disconnect();
+ clearInterval(intervalId);
+ reject(new Error(`Could not scroll target into viewPort within ${timeout * maxTries} ms`));
+ }
+ retry += 1;
+ intersectionTarget.scrollIntoView();
+ }, timeout);
+
+ intersectionObserver = new IntersectionObserver(entries => {
+ if (entries[0].isIntersecting) {
+ intersectionObserver.disconnect();
+ clearInterval(intervalId);
+ resolve();
+ }
+ });
+
+ intersectionObserver.observe(intersectionTarget);
+
+ intersectionTarget.scrollIntoView();
+ });
+}
diff --git a/spec/frontend/helpers/set_timeout_promise_helper.js b/spec/frontend/helpers/set_timeout_promise_helper.js
new file mode 100644
index 00000000000..47087619187
--- /dev/null
+++ b/spec/frontend/helpers/set_timeout_promise_helper.js
@@ -0,0 +1,4 @@
+export default (time = 0) =>
+ new Promise(resolve => {
+ setTimeout(resolve, time);
+ });
diff --git a/spec/frontend/helpers/user_mock_data_helper.js b/spec/frontend/helpers/user_mock_data_helper.js
new file mode 100644
index 00000000000..6999fa1f8a1
--- /dev/null
+++ b/spec/frontend/helpers/user_mock_data_helper.js
@@ -0,0 +1,14 @@
+export default {
+ createNumberRandomUsers(numberUsers) {
+ const users = [];
+ for (let i = 0; i < numberUsers; i += 1) {
+ users.push({
+ avatar: 'https://gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ id: i + 1,
+ name: `GitLab User ${i}`,
+ username: `gitlab${i}`,
+ });
+ }
+ return users;
+ },
+};
diff --git a/spec/frontend/helpers/vue_component_helper.js b/spec/frontend/helpers/vue_component_helper.js
new file mode 100644
index 00000000000..e0fe18e5560
--- /dev/null
+++ b/spec/frontend/helpers/vue_component_helper.js
@@ -0,0 +1,18 @@
+/**
+ * Replaces line break with an empty space
+ * @param {*} data
+ */
+export const removeBreakLine = data => data.replace(/\r?\n|\r/g, ' ');
+
+/**
+ * Removes line breaks, spaces and trims the given text
+ * @param {String} str
+ * @returns {String}
+ */
+export const trimText = str =>
+ str
+ .replace(/\r?\n|\r/g, '')
+ .replace(/\s\s+/g, ' ')
+ .trim();
+
+export const removeWhitespace = str => str.replace(/\s\s+/g, ' ');
diff --git a/spec/frontend/helpers/vue_resource_helper.js b/spec/frontend/helpers/vue_resource_helper.js
new file mode 100644
index 00000000000..0f58af09933
--- /dev/null
+++ b/spec/frontend/helpers/vue_resource_helper.js
@@ -0,0 +1,11 @@
+// eslint-disable-next-line import/prefer-default-export
+export const headersInterceptor = (request, next) => {
+ next(response => {
+ const headers = {};
+ response.headers.forEach((value, key) => {
+ headers[key] = value;
+ });
+ // eslint-disable-next-line no-param-reassign
+ response.headers = headers;
+ });
+};
diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js
new file mode 100644
index 00000000000..19e27388eeb
--- /dev/null
+++ b/spec/frontend/helpers/vue_test_utils_helper.js
@@ -0,0 +1,19 @@
+/* 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);
+
+/**
+ * Determines whether a `shallowMount` Wrapper contains text
+ * within one of it's slots. This will also work on Wrappers
+ * acquired with `find()`, but only if it's parent Wrapper
+ * was shallowMounted.
+ * NOTE: Prefer checking the rendered output of a component
+ * wherever possible using something like `text()` instead.
+ * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted)
+ * @param {String} slotName
+ * @param {String} text
+ */
+export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
+ !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length;
diff --git a/spec/frontend/helpers/vuex_action_helper.js b/spec/frontend/helpers/vuex_action_helper.js
new file mode 100644
index 00000000000..88652202a8e
--- /dev/null
+++ b/spec/frontend/helpers/vuex_action_helper.js
@@ -0,0 +1,104 @@
+const noop = () => {};
+
+/**
+ * Helper for testing action with expected mutations inspired in
+ * https://vuex.vuejs.org/en/testing.html
+ *
+ * @param {Function} action to be tested
+ * @param {Object} payload will be provided to the action
+ * @param {Object} state will be provided to the action
+ * @param {Array} [expectedMutations=[]] mutations expected to be committed
+ * @param {Array} [expectedActions=[]] actions expected to be dispatched
+ * @param {Function} [done=noop] to be executed after the tests
+ * @return {Promise}
+ *
+ * @example
+ * testAction(
+ * actions.actionName, // action
+ * { }, // mocked payload
+ * state, //state
+ * // expected mutations
+ * [
+ * { type: types.MUTATION}
+ * { type: types.MUTATION_1, payload: jasmine.any(Number)}
+ * ],
+ * // expected actions
+ * [
+ * { type: 'actionName', payload: {param: 'foobar'}},
+ * { type: 'actionName1'}
+ * ]
+ * done,
+ * );
+ *
+ * @example
+ * testAction(
+ * actions.actionName, // action
+ * { }, // mocked payload
+ * state, //state
+ * [ { type: types.MUTATION} ], // expected mutations
+ * [], // expected actions
+ * ).then(done)
+ * .catch(done.fail);
+ */
+export default (
+ action,
+ payload,
+ state,
+ expectedMutations = [],
+ expectedActions = [],
+ done = noop,
+) => {
+ const mutations = [];
+ const actions = [];
+
+ // mock commit
+ const commit = (type, mutationPayload) => {
+ const mutation = { type };
+
+ if (typeof mutationPayload !== 'undefined') {
+ mutation.payload = mutationPayload;
+ }
+
+ mutations.push(mutation);
+ };
+
+ // mock dispatch
+ const dispatch = (type, actionPayload) => {
+ const dispatchedAction = { type };
+
+ if (typeof actionPayload !== 'undefined') {
+ dispatchedAction.payload = actionPayload;
+ }
+
+ actions.push(dispatchedAction);
+ };
+
+ const validateResults = () => {
+ expect({
+ mutations,
+ actions,
+ }).toEqual({
+ mutations: expectedMutations,
+ actions: expectedActions,
+ });
+ done();
+ };
+
+ const result = action(
+ { commit, state, dispatch, rootState: state, rootGetters: state, getters: state },
+ payload,
+ );
+
+ return new Promise(resolve => {
+ setImmediate(resolve);
+ })
+ .then(() => result)
+ .catch(error => {
+ validateResults();
+ throw error;
+ })
+ .then(data => {
+ validateResults();
+ return data;
+ });
+};
diff --git a/spec/frontend/helpers/wait_for_attribute_change.js b/spec/frontend/helpers/wait_for_attribute_change.js
new file mode 100644
index 00000000000..8f22d569222
--- /dev/null
+++ b/spec/frontend/helpers/wait_for_attribute_change.js
@@ -0,0 +1,16 @@
+export default (domElement, attributes, timeout = 1500) =>
+ new Promise((resolve, reject) => {
+ let observer;
+ const timeoutId = setTimeout(() => {
+ observer.disconnect();
+ reject(new Error(`Could not see an attribute update within ${timeout} ms`));
+ }, timeout);
+
+ observer = new MutationObserver(() => {
+ clearTimeout(timeoutId);
+ observer.disconnect();
+ resolve();
+ });
+
+ observer.observe(domElement, { attributes: true, attributeFilter: attributes });
+ });
diff --git a/spec/frontend/helpers/wait_for_promises.js b/spec/frontend/helpers/wait_for_promises.js
new file mode 100644
index 00000000000..1d2b53fc770
--- /dev/null
+++ b/spec/frontend/helpers/wait_for_promises.js
@@ -0,0 +1 @@
+export default () => new Promise(resolve => requestAnimationFrame(resolve));
diff --git a/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap
new file mode 100644
index 00000000000..5f9f13d591d
--- /dev/null
+++ b/spec/frontend/mr_popover/__snapshots__/mr_popover_spec.js.snap
@@ -0,0 +1,93 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`MR Popover loaded state matches the snapshot 1`] = `
+<glpopover-stub
+ boundary="viewport"
+ placement="top"
+ show=""
+ target=""
+>
+ <div
+ class="mr-popover"
+ >
+ <div
+ class="d-flex align-items-center justify-content-between"
+ >
+ <div
+ class="d-inline-flex align-items-center"
+ >
+ <div
+ class="issuable-status-box status-box status-box-open"
+ >
+
+ Open
+
+ </div>
+
+ <span
+ class="text-secondary"
+ >
+ Opened
+ <time>
+ just now
+ </time>
+ </span>
+ </div>
+
+ <ciicon-stub
+ cssclasses=""
+ size="16"
+ status="[object Object]"
+ />
+ </div>
+
+ <h5
+ class="my-2"
+ >
+ MR Title
+ </h5>
+
+ <div
+ class="text-secondary"
+ >
+
+ foo/bar!1
+
+ </div>
+ </div>
+</glpopover-stub>
+`;
+
+exports[`MR Popover shows skeleton-loader while apollo is loading 1`] = `
+<glpopover-stub
+ boundary="viewport"
+ placement="top"
+ show=""
+ target=""
+>
+ <div
+ class="mr-popover"
+ >
+ <div>
+ <glskeletonloading-stub
+ class="animation-container-small mt-1"
+ lines="1"
+ />
+ </div>
+
+ <h5
+ class="my-2"
+ >
+ MR Title
+ </h5>
+
+ <div
+ class="text-secondary"
+ >
+
+ foo/bar!1
+
+ </div>
+ </div>
+</glpopover-stub>
+`;
diff --git a/spec/frontend/mr_popover/mr_popover_spec.js b/spec/frontend/mr_popover/mr_popover_spec.js
new file mode 100644
index 00000000000..79ed4163010
--- /dev/null
+++ b/spec/frontend/mr_popover/mr_popover_spec.js
@@ -0,0 +1,61 @@
+import MRPopover from '~/mr_popover/components/mr_popover';
+import { shallowMount } from '@vue/test-utils';
+
+describe('MR Popover', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(MRPopover, {
+ propsData: {
+ target: document.createElement('a'),
+ projectPath: 'foo/bar',
+ mergeRequestIID: '1',
+ mergeRequestTitle: 'MR Title',
+ },
+ mocks: {
+ $apollo: {
+ loading: false,
+ },
+ },
+ });
+ });
+
+ it('shows skeleton-loader while apollo is loading', () => {
+ wrapper.vm.$apollo.loading = true;
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ describe('loaded state', () => {
+ it('matches the snapshot', () => {
+ wrapper.setData({
+ mergeRequest: {
+ state: 'opened',
+ createdAt: new Date(),
+ headPipeline: {
+ detailedStatus: {
+ group: 'success',
+ status: 'status_success',
+ },
+ },
+ },
+ });
+
+ expect(wrapper.element).toMatchSnapshot();
+ });
+
+ it('does not show CI Icon if there is no pipeline data', () => {
+ wrapper.setData({
+ mergeRequest: {
+ state: 'opened',
+ headPipeline: null,
+ stateHumanName: 'Open',
+ title: 'Merge Request Title',
+ createdAt: new Date(),
+ },
+ });
+
+ expect(wrapper.contains('ciicon-stub')).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js
index 23d07056925..1e0bc708c31 100644
--- a/spec/javascripts/pages/admin/abuse_reports/abuse_reports_spec.js
+++ b/spec/frontend/pages/admin/abuse_reports/abuse_reports_spec.js
@@ -3,7 +3,7 @@ import '~/lib/utils/text_utility';
import AbuseReports from '~/pages/admin/abuse_reports/abuse_reports';
describe('Abuse Reports', () => {
- const FIXTURE = 'abuse_reports/abuse_reports_list.html.raw';
+ const FIXTURE = 'abuse_reports/abuse_reports_list.html';
const MAX_MESSAGE_LENGTH = 500;
let $messages;
@@ -16,9 +16,9 @@ describe('Abuse Reports', () => {
preloadFixtures(FIXTURE);
- beforeEach(function() {
+ beforeEach(() => {
loadFixtures(FIXTURE);
- this.abuseReports = new AbuseReports();
+ new AbuseReports(); // eslint-disable-line no-new
$messages = $('.abuse-reports .message');
});
diff --git a/spec/javascripts/reports/components/report_section_spec.js b/spec/frontend/reports/components/report_section_spec.js
index b02af8baaec..3b609484b9e 100644
--- a/spec/javascripts/reports/components/report_section_spec.js
+++ b/spec/frontend/reports/components/report_section_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import reportSection from '~/reports/components/report_section.vue';
-import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper';
+import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
describe('Report section', () => {
let vm;
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index 006fc60ef57..c57e0e7cfc6 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -1,12 +1,19 @@
import Vue from 'vue';
+import * as jqueryMatchers from 'custom-jquery-matchers';
import Translate from '~/vue_shared/translate';
import axios from '~/lib/utils/axios_utils';
import { initializeTestTimeout } from './helpers/timeout';
+import { getJSONFixture, loadHTMLFixture, setHTMLFixture } from './helpers/fixtures';
-// wait for pending setTimeout()s
-afterEach(() => {
- jest.runAllTimers();
-});
+process.on('unhandledRejection', global.promiseRejectionHandler);
+
+afterEach(() =>
+ // give Promises a bit more time so they fail the right test
+ new Promise(setImmediate).then(() => {
+ // wait for pending setTimeout()s
+ jest.runAllTimers();
+ }),
+);
initializeTestTimeout(300);
@@ -22,4 +29,31 @@ beforeEach(done => {
done();
});
+Vue.config.devtools = false;
+Vue.config.productionTip = false;
+
Vue.use(Translate);
+
+// workaround for JSDOM not supporting innerText
+// see https://github.com/jsdom/jsdom/issues/1245
+Object.defineProperty(global.Element.prototype, 'innerText', {
+ get() {
+ return this.textContent;
+ },
+ configurable: true, // make it so that it doesn't blow chunks on re-running tests with things like --watch
+});
+
+// convenience wrapper for migration from Karma
+Object.assign(global, {
+ loadFixtures: loadHTMLFixture,
+ loadJSONFixtures: getJSONFixture,
+ preloadFixtures() {},
+ setFixtures: setHTMLFixture,
+});
+
+// custom-jquery-matchers was written for an old Jest version, we need to make it compatible
+Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => {
+ expect.extend({
+ [matcherName]: matcherFactory().compare,
+ });
+});
diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb
new file mode 100644
index 00000000000..a21162adb42
--- /dev/null
+++ b/spec/graphql/types/ci/detailed_status_type_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe Types::Ci::DetailedStatusType do
+ it { expect(described_class.graphql_name).to eq('DetailedStatus') }
+
+ it "has all fields" do
+ expect(described_class).to have_graphql_fields(:group, :icon, :favicon,
+ :details_path, :has_details,
+ :label, :text, :tooltip)
+ end
+end
diff --git a/spec/graphql/types/permission_types/issue_spec.rb b/spec/graphql/types/permission_types/issue_spec.rb
index c3f84629aa2..f0fbeda202f 100644
--- a/spec/graphql/types/permission_types/issue_spec.rb
+++ b/spec/graphql/types/permission_types/issue_spec.rb
@@ -7,6 +7,8 @@ describe Types::PermissionTypes::Issue do
:create_note, :reopen_issue
]
- expect(described_class).to have_graphql_fields(expected_permissions)
+ expected_permissions.each do |permission|
+ expect(described_class).to have_graphql_field(permission)
+ end
end
end
diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb
index 4288412eda3..4974995b587 100644
--- a/spec/graphql/types/permission_types/project_spec.rb
+++ b/spec/graphql/types/permission_types/project_spec.rb
@@ -13,6 +13,8 @@ describe Types::PermissionTypes::Project do
:update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content
]
- expect(described_class).to have_graphql_fields(expected_permissions)
+ expected_permissions.each do |permission|
+ expect(described_class).to have_graphql_field(permission)
+ end
end
end
diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb
index 8d717b968dd..a3511e078ce 100644
--- a/spec/helpers/appearances_helper_spec.rb
+++ b/spec/helpers/appearances_helper_spec.rb
@@ -65,12 +65,10 @@ describe AppearancesHelper do
end
describe '#brand_title' do
- it 'returns the default CE title when no appearance is present' do
- allow(helper)
- .to receive(:current_appearance)
- .and_return(nil)
+ it 'returns the default title when no appearance is present' do
+ allow(helper).to receive(:current_appearance).and_return(nil)
- expect(helper.brand_title).to eq('GitLab Community Edition')
+ expect(helper.brand_title).to eq(helper.default_brand_title)
end
end
end
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index 2ba8b3dbf22..aae515def0c 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe AuthHelper do
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 540a8674ec2..1763c46389a 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupsHelper do
@@ -227,4 +229,37 @@ describe GroupsHelper do
expect(helper.group_sidebar_links).not_to include(*cross_project_features)
end
end
+
+ describe 'parent_group_options', :nested_groups do
+ let(:current_user) { create(:user) }
+ let(:group) { create(:group, name: 'group') }
+ let(:group2) { create(:group, name: 'group2') }
+
+ before do
+ group.add_owner(current_user)
+ group2.add_owner(current_user)
+ end
+
+ it 'includes explicitly owned groups except self' do
+ expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json)
+ end
+
+ it 'excludes parent group' do
+ subgroup = create(:group, parent: group2)
+
+ expect(parent_group_options(subgroup)).to eq([{ id: group.id, text: group.human_name }].to_json)
+ end
+
+ it 'includes subgroups with inherited ownership' do
+ subgroup = create(:group, parent: group)
+
+ expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }, { id: subgroup.id, text: subgroup.human_name }].to_json)
+ end
+
+ it 'excludes own subgroups' do
+ create(:group, parent: group2)
+
+ expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json)
+ end
+ end
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 8b82dea2524..1d1446eaa30 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssuablesHelper do
@@ -176,7 +178,7 @@ describe IssuablesHelper do
stub_commonmark_sourcepos_disabled
end
- it 'returns the correct json for an issue' do
+ it 'returns the correct data for an issue' do
issue = create(:issue, author: user, description: 'issue text')
@project = issue.project
@@ -198,7 +200,7 @@ describe IssuablesHelper do
initialDescriptionText: 'issue text',
initialTaskStatus: '0 of 0 tasks completed'
}
- expect(helper.issuable_initial_data(issue)).to eq(expected_data)
+ expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data))
end
end
end
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 885204062fe..193390d2f2c 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequestsHelper do
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 291eafece94..37c63807c82 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectsHelper do
diff --git a/spec/javascripts/activities_spec.js b/spec/javascripts/activities_spec.js
index 068b8eb65bc..23b6de7e4e0 100644
--- a/spec/javascripts/activities_spec.js
+++ b/spec/javascripts/activities_spec.js
@@ -7,7 +7,7 @@ import Pager from '~/pager';
describe('Activities', () => {
window.gon || (window.gon = {});
- const fixtureTemplate = 'static/event_filter.html.raw';
+ const fixtureTemplate = 'static/event_filter.html';
const filters = [
{
id: 'all',
diff --git a/spec/javascripts/ajax_loading_spinner_spec.js b/spec/javascripts/ajax_loading_spinner_spec.js
index 9389fc94f17..89195a4397f 100644
--- a/spec/javascripts/ajax_loading_spinner_spec.js
+++ b/spec/javascripts/ajax_loading_spinner_spec.js
@@ -2,7 +2,7 @@ import $ from 'jquery';
import AjaxLoadingSpinner from '~/ajax_loading_spinner';
describe('Ajax Loading Spinner', () => {
- const fixtureTemplate = 'static/ajax_loading_spinner.html.raw';
+ const fixtureTemplate = 'static/ajax_loading_spinner.html';
preloadFixtures(fixtureTemplate);
beforeEach(() => {
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index e5b5707dcef..e10df1b45e7 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -24,13 +24,13 @@ const lazyAssert = function(done, assertFn) {
describe('AwardsHandler', function() {
const emojiData = getJSONFixture('emojis/emojis.json');
- preloadFixtures('snippets/show.html.raw');
+ preloadFixtures('snippets/show.html');
beforeEach(function(done) {
mock = new MockAdapter(axios);
mock.onGet(`/-/emojis/${EMOJI_VERSION}/emojis.json`).reply(200, emojiData);
- loadFixtures('snippets/show.html.raw');
+ loadFixtures('snippets/show.html');
loadAwardsHandler(true)
.then(obj => {
awardsHandler = obj;
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 681463aab66..7af8c984841 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -4,10 +4,10 @@ import '~/behaviors/quick_submit';
describe('Quick Submit behavior', function() {
const keydownEvent = (options = { keyCode: 13, metaKey: true }) => $.Event('keydown', options);
- preloadFixtures('snippets/show.html.raw');
+ preloadFixtures('snippets/show.html');
beforeEach(() => {
- loadFixtures('snippets/show.html.raw');
+ loadFixtures('snippets/show.html');
$('form').submit(e => {
// Prevent a form submit from moving us off the testing page
e.preventDefault();
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index 1bde2bb3024..617fe49b059 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -3,10 +3,10 @@ import '~/behaviors/requires_input';
describe('requiresInput', () => {
let submitButton;
- preloadFixtures('branches/new_branch.html.raw');
+ preloadFixtures('branches/new_branch.html');
beforeEach(() => {
- loadFixtures('branches/new_branch.html.raw');
+ loadFixtures('branches/new_branch.html');
submitButton = $('button[type="submit"]');
});
diff --git a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
index 4843a0386b5..5e457a4e823 100644
--- a/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -9,7 +9,7 @@ import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form';
describe('ShortcutsIssuable', function() {
- const fixtureName = 'snippets/show.html.raw';
+ const fixtureName = 'snippets/show.html';
preloadFixtures(fixtureName);
beforeAll(done => {
diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js
index 5f027f59fcf..68b4f261617 100644
--- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js
+++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_integration_spec.js
@@ -6,10 +6,10 @@ describe('Balsamiq integration spec', () => {
let endpoint;
let balsamiqViewer;
- preloadFixtures('static/balsamiq_viewer.html.raw');
+ preloadFixtures('static/balsamiq_viewer.html');
beforeEach(() => {
- loadFixtures('static/balsamiq_viewer.html.raw');
+ loadFixtures('static/balsamiq_viewer.html');
container = document.getElementById('js-balsamiq-viewer');
balsamiqViewer = new BalsamiqViewer(container);
diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js
index 432d8a65b0a..cab06a0a9be 100644
--- a/spec/javascripts/blob/blob_file_dropzone_spec.js
+++ b/spec/javascripts/blob/blob_file_dropzone_spec.js
@@ -2,10 +2,10 @@ import $ from 'jquery';
import BlobFileDropzone from '~/blob/blob_file_dropzone';
describe('BlobFileDropzone', function() {
- preloadFixtures('blob/show.html.raw');
+ preloadFixtures('blob/show.html');
beforeEach(() => {
- loadFixtures('blob/show.html.raw');
+ loadFixtures('blob/show.html');
const form = $('.js-upload-blob-form');
this.blobFileDropzone = new BlobFileDropzone(form, 'POST');
this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone;
diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js
index 28d3b2f5ea3..6bb5bac007f 100644
--- a/spec/javascripts/blob/notebook/index_spec.js
+++ b/spec/javascripts/blob/notebook/index_spec.js
@@ -3,10 +3,10 @@ import axios from '~/lib/utils/axios_utils';
import renderNotebook from '~/blob/notebook';
describe('iPython notebook renderer', () => {
- preloadFixtures('static/notebook_viewer.html.raw');
+ preloadFixtures('static/notebook_viewer.html');
beforeEach(() => {
- loadFixtures('static/notebook_viewer.html.raw');
+ loadFixtures('static/notebook_viewer.html');
});
it('shows loading icon', () => {
diff --git a/spec/javascripts/blob/pdf/index_spec.js b/spec/javascripts/blob/pdf/index_spec.js
index be917a0613f..acf87580777 100644
--- a/spec/javascripts/blob/pdf/index_spec.js
+++ b/spec/javascripts/blob/pdf/index_spec.js
@@ -15,10 +15,10 @@ describe('PDF renderer', () => {
}
};
- preloadFixtures('static/pdf_viewer.html.raw');
+ preloadFixtures('static/pdf_viewer.html');
beforeEach(() => {
- loadFixtures('static/pdf_viewer.html.raw');
+ loadFixtures('static/pdf_viewer.html');
viewer = document.getElementById('js-pdf-viewer');
viewer.dataset.endpoint = testPDF;
});
diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js
index 2b1e81e9cbc..3d3129e10da 100644
--- a/spec/javascripts/blob/sketch/index_spec.js
+++ b/spec/javascripts/blob/sketch/index_spec.js
@@ -13,10 +13,10 @@ describe('Sketch viewer', () => {
});
};
- preloadFixtures('static/sketch_viewer.html.raw');
+ preloadFixtures('static/sketch_viewer.html');
beforeEach(() => {
- loadFixtures('static/sketch_viewer.html.raw');
+ loadFixtures('static/sketch_viewer.html');
});
describe('with error message', () => {
diff --git a/spec/javascripts/blob/viewer/index_spec.js b/spec/javascripts/blob/viewer/index_spec.js
index 93a942fe8d4..4ac15ca5aa2 100644
--- a/spec/javascripts/blob/viewer/index_spec.js
+++ b/spec/javascripts/blob/viewer/index_spec.js
@@ -9,12 +9,12 @@ describe('Blob viewer', () => {
let blob;
let mock;
- preloadFixtures('snippets/show.html.raw');
+ preloadFixtures('snippets/show.html');
beforeEach(() => {
mock = new MockAdapter(axios);
- loadFixtures('snippets/show.html.raw');
+ loadFixtures('snippets/show.html');
$('#modal-upload-blob').remove();
blob = new BlobViewer();
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
index dee7841c088..6e6b3e6950b 100644
--- a/spec/javascripts/boards/components/board_spec.js
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -9,7 +9,7 @@ describe('Board component', () => {
let el;
beforeEach(done => {
- loadFixtures('boards/show.html.raw');
+ loadFixtures('boards/show.html');
el = document.createElement('div');
document.body.appendChild(el);
diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js b/spec/javascripts/bootstrap_linked_tabs_spec.js
index c3e3d78ff63..1d21637ceae 100644
--- a/spec/javascripts/bootstrap_linked_tabs_spec.js
+++ b/spec/javascripts/bootstrap_linked_tabs_spec.js
@@ -1,10 +1,10 @@
import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
describe('Linked Tabs', () => {
- preloadFixtures('static/linked_tabs.html.raw');
+ preloadFixtures('static/linked_tabs.html');
beforeEach(() => {
- loadFixtures('static/linked_tabs.html.raw');
+ loadFixtures('static/linked_tabs.html');
});
describe('when is initialized', () => {
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index 1fc0e206d5e..481b1a4d4b0 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -7,8 +7,8 @@ const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-proje
const HIDE_CLASS = 'hide';
describe('AjaxFormVariableList', () => {
- preloadFixtures('projects/ci_cd_settings.html.raw');
- preloadFixtures('projects/ci_cd_settings_with_variables.html.raw');
+ preloadFixtures('projects/ci_cd_settings.html');
+ preloadFixtures('projects/ci_cd_settings_with_variables.html');
let container;
let saveButton;
@@ -18,7 +18,7 @@ describe('AjaxFormVariableList', () => {
let ajaxVariableList;
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings.html.raw');
+ loadFixtures('projects/ci_cd_settings.html');
container = document.querySelector('.js-ci-variable-list-section');
mock = new MockAdapter(axios);
@@ -168,7 +168,7 @@ describe('AjaxFormVariableList', () => {
describe('updateRowsWithPersistedVariables', () => {
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings_with_variables.html.raw');
+ loadFixtures('projects/ci_cd_settings_with_variables.html');
container = document.querySelector('.js-ci-variable-list-section');
const ajaxVariableListEl = document.querySelector('.js-ci-variable-list-section');
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index bef59b86d0c..70f49469300 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -5,9 +5,9 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
const HIDE_CLASS = 'hide';
describe('VariableList', () => {
- preloadFixtures('pipeline_schedules/edit.html.raw');
- preloadFixtures('pipeline_schedules/edit_with_variables.html.raw');
- preloadFixtures('projects/ci_cd_settings.html.raw');
+ preloadFixtures('pipeline_schedules/edit.html');
+ preloadFixtures('pipeline_schedules/edit_with_variables.html');
+ preloadFixtures('projects/ci_cd_settings.html');
let $wrapper;
let variableList;
@@ -15,7 +15,7 @@ describe('VariableList', () => {
describe('with only key/value inputs', () => {
describe('with no variables', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit.html.raw');
+ loadFixtures('pipeline_schedules/edit.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -82,7 +82,7 @@ describe('VariableList', () => {
describe('with persisted variables', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit_with_variables.html.raw');
+ loadFixtures('pipeline_schedules/edit_with_variables.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -115,7 +115,7 @@ describe('VariableList', () => {
describe('with all inputs(key, value, protected)', () => {
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings.html.raw');
+ loadFixtures('projects/ci_cd_settings.html');
$wrapper = $('.js-ci-variable-list-section');
$wrapper.find('.js-ci-variable-input-protected').attr('data-default', 'false');
@@ -149,7 +149,7 @@ describe('VariableList', () => {
describe('toggleEnableRow method', () => {
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit_with_variables.html.raw');
+ loadFixtures('pipeline_schedules/edit_with_variables.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
@@ -198,7 +198,7 @@ describe('VariableList', () => {
describe('hideValues', () => {
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings.html.raw');
+ loadFixtures('projects/ci_cd_settings.html');
$wrapper = $('.js-ci-variable-list-section');
variableList = new VariableList({
diff --git a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
index 997d0d54d79..4982b68fa81 100644
--- a/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/native_form_variable_list_spec.js
@@ -2,12 +2,12 @@ import $ from 'jquery';
import setupNativeFormVariableList from '~/ci_variable_list/native_form_variable_list';
describe('NativeFormVariableList', () => {
- preloadFixtures('pipeline_schedules/edit.html.raw');
+ preloadFixtures('pipeline_schedules/edit.html');
let $wrapper;
beforeEach(() => {
- loadFixtures('pipeline_schedules/edit.html.raw');
+ loadFixtures('pipeline_schedules/edit.html');
$wrapper = $('.js-ci-variable-list-section');
setupNativeFormVariableList({
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
index 71f16dc259e..0d3dcc29f22 100644
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ b/spec/javascripts/clusters/clusters_bundle_spec.js
@@ -9,10 +9,10 @@ import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
describe('Clusters', () => {
let cluster;
- preloadFixtures('clusters/show_cluster.html.raw');
+ preloadFixtures('clusters/show_cluster.html');
beforeEach(() => {
- loadFixtures('clusters/show_cluster.html.raw');
+ loadFixtures('clusters/show_cluster.html');
cluster = new Clusters();
});
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
index 790e4b9602c..0f8153ad493 100644
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ b/spec/javascripts/clusters/components/applications_spec.js
@@ -79,7 +79,7 @@ describe('Applications', () => {
});
it('renders a row for GitLab Runner', () => {
- expect(vm.$el.querySelector('.js-cluster-application-row-runner')).toBeNull();
+ expect(vm.$el.querySelector('.js-cluster-application-row-runner')).not.toBeNull();
});
it('renders a row for Jupyter', () => {
diff --git a/spec/javascripts/collapsed_sidebar_todo_spec.js b/spec/javascripts/collapsed_sidebar_todo_spec.js
index dc5737558c0..bb90e53e525 100644
--- a/spec/javascripts/collapsed_sidebar_todo_spec.js
+++ b/spec/javascripts/collapsed_sidebar_todo_spec.js
@@ -6,7 +6,7 @@ import Sidebar from '~/right_sidebar';
import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Issuable right sidebar collapsed todo toggle', () => {
- const fixtureName = 'issues/open-issue.html.raw';
+ const fixtureName = 'issues/open-issue.html';
const jsonFixtureName = 'todos/todos.json';
let mock;
diff --git a/spec/javascripts/create_item_dropdown_spec.js b/spec/javascripts/create_item_dropdown_spec.js
index 9cf72d7c55b..a814952faab 100644
--- a/spec/javascripts/create_item_dropdown_spec.js
+++ b/spec/javascripts/create_item_dropdown_spec.js
@@ -20,7 +20,7 @@ const DROPDOWN_ITEM_DATA = [
];
describe('CreateItemDropdown', () => {
- preloadFixtures('static/create_item_dropdown.html.raw');
+ preloadFixtures('static/create_item_dropdown.html');
let $wrapperEl;
let createItemDropdown;
@@ -44,7 +44,7 @@ describe('CreateItemDropdown', () => {
}
beforeEach(() => {
- loadFixtures('static/create_item_dropdown.html.raw');
+ loadFixtures('static/create_item_dropdown.html');
$wrapperEl = $('.js-create-item-dropdown-fixture-root');
});
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index 66c5b17b825..6614069f44d 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -3,7 +3,7 @@ import Vuex from 'vuex';
import diffsModule from '~/diffs/store/modules';
import notesModule from '~/notes/stores/modules';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
-import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import mountComponent, { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffDiscussionsMockData from '../mock_data/diff_discussions';
import { diffViewerModes } from '~/ide/constants';
@@ -23,9 +23,6 @@ describe('diff_file_header', () => {
});
beforeEach(() => {
- gon.features = {
- expandDiffFullFile: true,
- };
const diffFile = diffDiscussionMock.diff_file;
diffFile.added_lines = 2;
@@ -252,6 +249,75 @@ describe('diff_file_header', () => {
expect(vm.$emit).not.toHaveBeenCalled();
});
});
+
+ describe('handleFileNameClick', () => {
+ let e;
+
+ beforeEach(() => {
+ e = { preventDefault: () => {} };
+ spyOn(e, 'preventDefault');
+ });
+
+ describe('when file name links to other page', () => {
+ it('does not call preventDefault if submodule tree url exists', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ diffFile: { ...props.diffFile, submodule_tree_url: 'foobar.com' },
+ });
+
+ vm.handleFileNameClick(e);
+
+ expect(e.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('does not call preventDefault if submodule_link exists', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ diffFile: { ...props.diffFile, submodule_link: 'foobar.com' },
+ });
+ vm.handleFileNameClick(e);
+
+ expect(e.preventDefault).not.toHaveBeenCalled();
+ });
+
+ it('does not call preventDefault if discussionPath exists', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ discussionPath: 'Foo bar',
+ });
+
+ vm.handleFileNameClick(e);
+
+ expect(e.preventDefault).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('scrolling to diff', () => {
+ let scrollToElement;
+ let el;
+
+ beforeEach(() => {
+ el = document.createElement('div');
+ spyOn(document, 'querySelector').and.returnValue(el);
+ scrollToElement = spyOnDependency(DiffFileHeader, 'scrollToElement');
+ vm = mountComponent(Component, props);
+
+ vm.handleFileNameClick(e);
+ });
+
+ it('calls scrollToElement with file content', () => {
+ expect(scrollToElement).toHaveBeenCalledWith(el);
+ });
+
+ it('element adds the content id to the window location', () => {
+ expect(window.location.hash).toContain(props.diffFile.file_hash);
+ });
+
+ it('calls preventDefault when button does not link to other page', () => {
+ expect(e.preventDefault).toHaveBeenCalled();
+ });
+ });
+ });
});
describe('template', () => {
diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js
index fd5dd611383..711ab543411 100644
--- a/spec/javascripts/diffs/mock_data/diff_discussions.js
+++ b/spec/javascripts/diffs/mock_data/diff_discussions.js
@@ -496,7 +496,7 @@ export default {
{
text: 'line',
rich_text:
- '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new noteable_line"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new noteable_line"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
+ '<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="1">\n1\n</td>\n<td class="line_content new"><span id="LC1" class="line" lang="plaintext"> - Bad dates</span>\n</td>\n</tr>\n<tr class="line_holder new" id="">\n<td class="diff-line-num new old_line" data-linenumber="1">\n \n</td>\n<td class="diff-line-num new new_line" data-linenumber="2">\n2\n</td>\n<td class="line_content new"><span id="LC2" class="line" lang="plaintext"></span>\n</td>\n</tr>\n',
can_receive_suggestion: true,
line_code: '6f209374f7e565f771b95720abf46024c41d1885_1_1',
type: 'new',
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js
index e8fcc8592eb..f764800fff0 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
@@ -72,7 +72,7 @@ describe('Dropdown User', () => {
});
describe('hideCurrentUser', () => {
- const fixtureTemplate = 'issues/issue_list.html.raw';
+ const fixtureTemplate = 'issues/issue_list.html';
preloadFixtures(fixtureTemplate);
let dropdown;
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index cfd0b96ec43..62d1bd69635 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -4,7 +4,7 @@ import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Dropdown Utils', () => {
- const issueListFixture = 'issues/issue_list.html.raw';
+ const issueListFixture = 'issues/issue_list.html';
preloadFixtures(issueListFixture);
describe('getEscapedText', () => {
diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore
index 0c35cdd778e..2507c8e7263 100644
--- a/spec/javascripts/fixtures/.gitignore
+++ b/spec/javascripts/fixtures/.gitignore
@@ -1,2 +1,3 @@
*.html.raw
+*.html
*.json
diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb
index 387858cba77..54b6419bcdb 100644
--- a/spec/javascripts/fixtures/abuse_reports.rb
+++ b/spec/javascripts/fixtures/abuse_reports.rb
@@ -18,7 +18,7 @@ describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controll
sign_in(admin)
end
- it 'abuse_reports/abuse_reports_list.html.raw' do |example|
+ it 'abuse_reports/abuse_reports_list.html' do |example|
get :index
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb
index 9989ac4fff2..76dbdf603da 100644
--- a/spec/javascripts/fixtures/admin_users.rb
+++ b/spec/javascripts/fixtures/admin_users.rb
@@ -17,7 +17,7 @@ describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('admin/users')
end
- it 'admin/users/new_with_internal_user_regex.html.raw' do |example|
+ it 'admin/users/new_with_internal_user_regex.html' do |example|
stub_application_setting(user_default_external: true)
stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb
index a9d3043f73d..c535e598e12 100644
--- a/spec/javascripts/fixtures/application_settings.rb
+++ b/spec/javascripts/fixtures/application_settings.rb
@@ -23,7 +23,7 @@ describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :c
remove_repository(project)
end
- it 'application_settings/accounts_and_limit.html.raw' do |example|
+ it 'application_settings/accounts_and_limit.html' do |example|
stub_application_setting(user_default_external: false)
get :show
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb
index cd66d98f92a..db7749bc000 100644
--- a/spec/javascripts/fixtures/blob.rb
+++ b/spec/javascripts/fixtures/blob.rb
@@ -22,7 +22,7 @@ describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
remove_repository(project)
end
- it 'blob/show.html.raw' do |example|
+ it 'blob/show.html' do |example|
get(:show, params: {
namespace_id: project.namespace,
project_id: project,
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb
index 1d675e008ba..c4390e89578 100644
--- a/spec/javascripts/fixtures/boards.rb
+++ b/spec/javascripts/fixtures/boards.rb
@@ -17,7 +17,7 @@ describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller
sign_in(admin)
end
- it 'boards/show.html.raw' do |example|
+ it 'boards/show.html' do |example|
get(:index, params: {
namespace_id: project.namespace,
project_id: project
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
index 3cc713ef90f..5d2d6c7ec0e 100644
--- a/spec/javascripts/fixtures/branches.rb
+++ b/spec/javascripts/fixtures/branches.rb
@@ -21,7 +21,7 @@ describe Projects::BranchesController, '(JavaScript fixtures)', type: :controlle
remove_repository(project)
end
- it 'branches/new_branch.html.raw' do |example|
+ it 'branches/new_branch.html' do |example|
get :new, params: {
namespace_id: project.namespace.to_param,
project_id: project
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb
index 69dbe54ffc2..8ebd8a41366 100644
--- a/spec/javascripts/fixtures/clusters.rb
+++ b/spec/javascripts/fixtures/clusters.rb
@@ -22,7 +22,7 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
remove_repository(project)
end
- it 'clusters/show_cluster.html.raw' do |example|
+ it 'clusters/show_cluster.html' do |example|
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb
index 295f13b34a4..ab10f559e4b 100644
--- a/spec/javascripts/fixtures/commit.rb
+++ b/spec/javascripts/fixtures/commit.rb
@@ -19,7 +19,7 @@ describe Projects::CommitController, '(JavaScript fixtures)', type: :controller
allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
end
- it 'commit/show.html.raw' do |example|
+ it 'commit/show.html' do |example|
params = {
namespace_id: project.namespace,
project_id: project,
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb
index 03136f4e661..16e31028b05 100644
--- a/spec/javascripts/fixtures/groups.rb
+++ b/spec/javascripts/fixtures/groups.rb
@@ -18,7 +18,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
end
describe GroupsController, '(JavaScript fixtures)', type: :controller do
- it 'groups/edit.html.raw' do |example|
+ it 'groups/edit.html' do |example|
get :edit, params: { id: group }
expect(response).to be_success
@@ -27,7 +27,7 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
end
describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
- it 'groups/ci_cd_settings.html.raw' do |example|
+ it 'groups/ci_cd_settings.html' do |example|
get :show, params: { group_id: group }
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
index 9b8e90c2a43..645b3aa788a 100644
--- a/spec/javascripts/fixtures/issues.rb
+++ b/spec/javascripts/fixtures/issues.rb
@@ -21,26 +21,26 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
remove_repository(project)
end
- it 'issues/open-issue.html.raw' do |example|
+ it 'issues/open-issue.html' do |example|
render_issue(example.description, create(:issue, project: project))
end
- it 'issues/closed-issue.html.raw' do |example|
+ it 'issues/closed-issue.html' do |example|
render_issue(example.description, create(:closed_issue, project: project))
end
- it 'issues/issue-with-task-list.html.raw' do |example|
+ it 'issues/issue-with-task-list.html' do |example|
issue = create(:issue, project: project, description: '- [ ] Task List Item')
render_issue(example.description, issue)
end
- it 'issues/issue_with_comment.html.raw' do |example|
+ it 'issues/issue_with_comment.html' do |example|
issue = create(:issue, project: project)
create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save
render_issue(example.description, issue)
end
- it 'issues/issue_list.html.raw' do |example|
+ it 'issues/issue_list.html' do |example|
create(:issue, project: project)
get :index, params: {
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb
index 433bb690a1c..941235190b5 100644
--- a/spec/javascripts/fixtures/jobs.rb
+++ b/spec/javascripts/fixtures/jobs.rb
@@ -32,7 +32,7 @@ describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
remove_repository(project)
end
- it 'builds/build-with-artifacts.html.raw' do |example|
+ it 'builds/build-with-artifacts.html' do |example|
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
index eb37be87e1d..7df1e5cb512 100644
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ b/spec/javascripts/fixtures/merge_requests.rb
@@ -42,19 +42,19 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
remove_repository(project)
end
- it 'merge_requests/merge_request_of_current_user.html.raw' do |example|
+ it 'merge_requests/merge_request_of_current_user.html' do |example|
merge_request.update(author: admin)
render_merge_request(example.description, merge_request)
end
- it 'merge_requests/merge_request_with_task_list.html.raw' do |example|
+ it 'merge_requests/merge_request_with_task_list.html' do |example|
create(:ci_build, :pending, pipeline: pipeline)
render_merge_request(example.description, merge_request)
end
- it 'merge_requests/merged_merge_request.html.raw' do |example|
+ it 'merge_requests/merged_merge_request.html' do |example|
expect_next_instance_of(MergeRequest) do |merge_request|
allow(merge_request).to receive(:source_branch_exists?).and_return(true)
allow(merge_request).to receive(:can_remove_source_branch?).and_return(true)
@@ -62,13 +62,13 @@ describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :cont
render_merge_request(example.description, merged_merge_request)
end
- it 'merge_requests/diff_comment.html.raw' do |example|
+ it 'merge_requests/diff_comment.html' do |example|
create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
render_merge_request(example.description, merge_request)
end
- it 'merge_requests/merge_request_with_comment.html.raw' do |example|
+ it 'merge_requests/merge_request_with_comment.html' do |example|
create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item')
render_merge_request(example.description, merge_request)
end
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb
index 05d79ec8de9..e5176a58273 100644
--- a/spec/javascripts/fixtures/pipeline_schedules.rb
+++ b/spec/javascripts/fixtures/pipeline_schedules.rb
@@ -21,7 +21,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
sign_in(admin)
end
- it 'pipeline_schedules/edit.html.raw' do |example|
+ it 'pipeline_schedules/edit.html' do |example|
get :edit, params: {
namespace_id: project.namespace.to_param,
project_id: project,
@@ -32,7 +32,7 @@ describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :
store_frontend_fixture(response, example.description)
end
- it 'pipeline_schedules/edit_with_variables.html.raw' do |example|
+ it 'pipeline_schedules/edit_with_variables.html' do |example|
get :edit, params: {
namespace_id: project.namespace.to_param,
project_id: project,
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
index 85f02923804..446da83a7f9 100644
--- a/spec/javascripts/fixtures/projects.rb
+++ b/spec/javascripts/fixtures/projects.rb
@@ -28,7 +28,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
end
describe ProjectsController, '(JavaScript fixtures)', type: :controller do
- it 'projects/dashboard.html.raw' do |example|
+ it 'projects/dashboard.html' do |example|
get :show, params: {
namespace_id: project.namespace.to_param,
id: project
@@ -38,7 +38,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
store_frontend_fixture(response, example.description)
end
- it 'projects/overview.html.raw' do |example|
+ it 'projects/overview.html' do |example|
get :show, params: {
namespace_id: project_with_repo.namespace.to_param,
id: project_with_repo
@@ -48,7 +48,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
store_frontend_fixture(response, example.description)
end
- it 'projects/edit.html.raw' do |example|
+ it 'projects/edit.html' do |example|
get :edit, params: {
namespace_id: project.namespace.to_param,
id: project
@@ -60,7 +60,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
end
describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
- it 'projects/ci_cd_settings.html.raw' do |example|
+ it 'projects/ci_cd_settings.html' do |example|
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project
@@ -70,7 +70,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
store_frontend_fixture(response, example.description)
end
- it 'projects/ci_cd_settings_with_variables.html.raw' do |example|
+ it 'projects/ci_cd_settings_with_variables.html' do |example|
create(:ci_variable, project: project_variable_populated)
create(:ci_variable, project: project_variable_populated)
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
index 746fbfd66dd..29dc95305b7 100644
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ b/spec/javascripts/fixtures/prometheus_service.rb
@@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
remove_repository(project)
end
- it 'services/prometheus/prometheus_service.html.raw' do |example|
+ it 'services/prometheus/prometheus_service.html' do |example|
get :edit, params: {
namespace_id: namespace,
project_id: project,
diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb
index 703cd3d49fa..5f5b4d4e60d 100644
--- a/spec/javascripts/fixtures/search.rb
+++ b/spec/javascripts/fixtures/search.rb
@@ -9,7 +9,7 @@ describe SearchController, '(JavaScript fixtures)', type: :controller do
clean_frontend_fixtures('search/')
end
- it 'search/show.html.raw' do |example|
+ it 'search/show.html' do |example|
get :show
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
index 6ccd74a07ff..dc7ee484c22 100644
--- a/spec/javascripts/fixtures/services.rb
+++ b/spec/javascripts/fixtures/services.rb
@@ -22,7 +22,7 @@ describe Projects::ServicesController, '(JavaScript fixtures)', type: :controlle
remove_repository(project)
end
- it 'services/edit_service.html.raw' do |example|
+ it 'services/edit_service.html' do |example|
get :edit, params: {
namespace_id: namespace,
project_id: project,
diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb
index e90a58e8c54..8656dea696a 100644
--- a/spec/javascripts/fixtures/sessions.rb
+++ b/spec/javascripts/fixtures/sessions.rb
@@ -16,7 +16,7 @@ describe 'Sessions (JavaScript fixtures)' do
set_devise_mapping(context: @request)
end
- it 'sessions/new.html.raw' do |example|
+ it 'sessions/new.html' do |example|
get :new
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb
index bcd6546f3df..ebc5b793166 100644
--- a/spec/javascripts/fixtures/snippet.rb
+++ b/spec/javascripts/fixtures/snippet.rb
@@ -23,7 +23,7 @@ describe SnippetsController, '(JavaScript fixtures)', type: :controller do
remove_repository(project)
end
- it 'snippets/show.html.raw' do |example|
+ it 'snippets/show.html' do |example|
create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item')
get(:show, params: { id: snippet.to_param })
diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw b/spec/javascripts/fixtures/static/ajax_loading_spinner.html
index 0e1ebb32b1c..0e1ebb32b1c 100644
--- a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw
+++ b/spec/javascripts/fixtures/static/ajax_loading_spinner.html
diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw b/spec/javascripts/fixtures/static/balsamiq_viewer.html
index cdd723d1a84..cdd723d1a84 100644
--- a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw
+++ b/spec/javascripts/fixtures/static/balsamiq_viewer.html
diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw b/spec/javascripts/fixtures/static/create_item_dropdown.html
index d2d38370092..d2d38370092 100644
--- a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw
+++ b/spec/javascripts/fixtures/static/create_item_dropdown.html
diff --git a/spec/javascripts/fixtures/static/event_filter.html.raw b/spec/javascripts/fixtures/static/event_filter.html
index 8e9b6fb1b5c..8e9b6fb1b5c 100644
--- a/spec/javascripts/fixtures/static/event_filter.html.raw
+++ b/spec/javascripts/fixtures/static/event_filter.html
diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html.raw b/spec/javascripts/fixtures/static/gl_dropdown.html
index 08f6738414e..08f6738414e 100644
--- a/spec/javascripts/fixtures/static/gl_dropdown.html.raw
+++ b/spec/javascripts/fixtures/static/gl_dropdown.html
diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html.raw b/spec/javascripts/fixtures/static/gl_field_errors.html
index f8470e02b7c..f8470e02b7c 100644
--- a/spec/javascripts/fixtures/static/gl_field_errors.html.raw
+++ b/spec/javascripts/fixtures/static/gl_field_errors.html
diff --git a/spec/javascripts/fixtures/static/issuable_filter.html.raw b/spec/javascripts/fixtures/static/issuable_filter.html
index 06b70fb43f1..06b70fb43f1 100644
--- a/spec/javascripts/fixtures/static/issuable_filter.html.raw
+++ b/spec/javascripts/fixtures/static/issuable_filter.html
diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw b/spec/javascripts/fixtures/static/issue_sidebar_label.html
index ec8fb30f219..ec8fb30f219 100644
--- a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw
+++ b/spec/javascripts/fixtures/static/issue_sidebar_label.html
diff --git a/spec/javascripts/fixtures/static/line_highlighter.html.raw b/spec/javascripts/fixtures/static/line_highlighter.html
index 897a25d6760..897a25d6760 100644
--- a/spec/javascripts/fixtures/static/line_highlighter.html.raw
+++ b/spec/javascripts/fixtures/static/line_highlighter.html
diff --git a/spec/javascripts/fixtures/static/linked_tabs.html.raw b/spec/javascripts/fixtures/static/linked_tabs.html
index c25463bf1db..c25463bf1db 100644
--- a/spec/javascripts/fixtures/static/linked_tabs.html.raw
+++ b/spec/javascripts/fixtures/static/linked_tabs.html
diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html.raw b/spec/javascripts/fixtures/static/merge_requests_show.html
index e219d9462aa..87e36c9f315 100644
--- a/spec/javascripts/fixtures/static/merge_requests_show.html.raw
+++ b/spec/javascripts/fixtures/static/merge_requests_show.html
@@ -1,7 +1,7 @@
<a class="btn-close"></a>
<div class="detail-page-description">
<div class="description js-task-list-container">
-<div class="wiki">
+<div class="md">
<ul class="task-list">
<li class="task-list-item">
<input class="task-list-item-checkbox" type="checkbox">
diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw b/spec/javascripts/fixtures/static/mini_dropdown_graph.html
index cd0b8dec3fc..cd0b8dec3fc 100644
--- a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw
+++ b/spec/javascripts/fixtures/static/mini_dropdown_graph.html
diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html.raw b/spec/javascripts/fixtures/static/notebook_viewer.html
index 4bbb7bf1094..4bbb7bf1094 100644
--- a/spec/javascripts/fixtures/static/notebook_viewer.html.raw
+++ b/spec/javascripts/fixtures/static/notebook_viewer.html
diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw b/spec/javascripts/fixtures/static/oauth_remember_me.html
index 9ba1ffc72fe..9ba1ffc72fe 100644
--- a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw
+++ b/spec/javascripts/fixtures/static/oauth_remember_me.html
diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html.raw b/spec/javascripts/fixtures/static/pdf_viewer.html
index 350d35a262f..350d35a262f 100644
--- a/spec/javascripts/fixtures/static/pdf_viewer.html.raw
+++ b/spec/javascripts/fixtures/static/pdf_viewer.html
diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html.raw b/spec/javascripts/fixtures/static/pipeline_graph.html
index 422372bb7d5..422372bb7d5 100644
--- a/spec/javascripts/fixtures/static/pipeline_graph.html.raw
+++ b/spec/javascripts/fixtures/static/pipeline_graph.html
diff --git a/spec/javascripts/fixtures/static/pipelines.html.raw b/spec/javascripts/fixtures/static/pipelines.html
index 42333f94f2f..42333f94f2f 100644
--- a/spec/javascripts/fixtures/static/pipelines.html.raw
+++ b/spec/javascripts/fixtures/static/pipelines.html
diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw b/spec/javascripts/fixtures/static/project_select_combo_button.html
index 50c826051c0..50c826051c0 100644
--- a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw
+++ b/spec/javascripts/fixtures/static/project_select_combo_button.html
diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html.raw b/spec/javascripts/fixtures/static/search_autocomplete.html
index 29db9020424..29db9020424 100644
--- a/spec/javascripts/fixtures/static/search_autocomplete.html.raw
+++ b/spec/javascripts/fixtures/static/search_autocomplete.html
diff --git a/spec/javascripts/fixtures/static/signin_tabs.html.raw b/spec/javascripts/fixtures/static/signin_tabs.html
index 7e66ab9394b..7e66ab9394b 100644
--- a/spec/javascripts/fixtures/static/signin_tabs.html.raw
+++ b/spec/javascripts/fixtures/static/signin_tabs.html
diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html.raw b/spec/javascripts/fixtures/static/sketch_viewer.html
index e25e554e568..e25e554e568 100644
--- a/spec/javascripts/fixtures/static/sketch_viewer.html.raw
+++ b/spec/javascripts/fixtures/static/sketch_viewer.html
diff --git a/spec/javascripts/fixtures/static_fixtures.rb b/spec/javascripts/fixtures/static_fixtures.rb
index b5188eeb994..cb4b90cdca5 100644
--- a/spec/javascripts/fixtures/static_fixtures.rb
+++ b/spec/javascripts/fixtures/static_fixtures.rb
@@ -4,7 +4,7 @@ describe ApplicationController, '(Static JavaScript fixtures)', type: :controlle
include JavaScriptFixturesHelpers
Dir.glob('{,ee/}spec/javascripts/fixtures/**/*.haml').map do |file_path|
- it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '.raw')}" do |example|
+ it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '')}" do |example|
store_frontend_fixture(render_template(file_path), example.description)
end
end
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
index b5f6620873b..6e37a2e5a4c 100644
--- a/spec/javascripts/fixtures/todos.rb
+++ b/spec/javascripts/fixtures/todos.rb
@@ -26,7 +26,7 @@ describe 'Todos (JavaScript fixtures)' do
sign_in(admin)
end
- it 'todos/todos.html.raw' do |example|
+ it 'todos/todos.html' do |example|
get :index
expect(response).to be_success
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
index 5cdbadef639..15866d65a4f 100644
--- a/spec/javascripts/fixtures/u2f.rb
+++ b/spec/javascripts/fixtures/u2f.rb
@@ -18,7 +18,7 @@ context 'U2F' do
set_devise_mapping(context: @request)
end
- it 'u2f/authenticate.html.raw' do |example|
+ it 'u2f/authenticate.html' do |example|
allow(controller).to receive(:find_user).and_return(user)
post :create, params: { user: { login: user.username, password: user.password } }
@@ -36,7 +36,7 @@ context 'U2F' do
allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares')
end
- it 'u2f/register.html.raw' do |example|
+ it 'u2f/register.html' do |example|
get :show
expect(response).to be_success
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index 85083653db8..57e31d933ca 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -5,7 +5,7 @@ import GLDropdown from '~/gl_dropdown';
import '~/lib/utils/common_utils';
describe('glDropdown', function describeDropdown() {
- preloadFixtures('static/gl_dropdown.html.raw');
+ preloadFixtures('static/gl_dropdown.html');
loadJSONFixtures('projects.json');
const NON_SELECTABLE_CLASSES =
@@ -64,7 +64,7 @@ describe('glDropdown', function describeDropdown() {
}
beforeEach(() => {
- loadFixtures('static/gl_dropdown.html.raw');
+ loadFixtures('static/gl_dropdown.html');
this.dropdownContainerElement = $('.dropdown.inline');
this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement);
this.projectsData = getJSONFixture('projects.json');
diff --git a/spec/javascripts/gl_field_errors_spec.js b/spec/javascripts/gl_field_errors_spec.js
index b463c9afbee..294f219d6fe 100644
--- a/spec/javascripts/gl_field_errors_spec.js
+++ b/spec/javascripts/gl_field_errors_spec.js
@@ -4,10 +4,10 @@ import $ from 'jquery';
import GlFieldErrors from '~/gl_field_errors';
describe('GL Style Field Errors', function() {
- preloadFixtures('static/gl_field_errors.html.raw');
+ preloadFixtures('static/gl_field_errors.html');
beforeEach(function() {
- loadFixtures('static/gl_field_errors.html.raw');
+ loadFixtures('static/gl_field_errors.html');
const $form = $('form.gl-show-field-errors');
this.$form = $form;
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index 2fe34e5a76f..0ddf589f368 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -3,7 +3,7 @@ import initTodoToggle from '~/header';
describe('Header', function() {
const todosPendingCount = '.todos-count';
- const fixtureTemplate = 'issues/open-issue.html.raw';
+ const fixtureTemplate = 'issues/open-issue.html';
function isTodosCountHidden() {
return $(todosPendingCount).hasClass('hidden');
diff --git a/spec/javascripts/ide/components/error_message_spec.js b/spec/javascripts/ide/components/error_message_spec.js
index 430e8e2baa3..80d6c7fd564 100644
--- a/spec/javascripts/ide/components/error_message_spec.js
+++ b/spec/javascripts/ide/components/error_message_spec.js
@@ -84,7 +84,7 @@ describe('IDE error message component', () => {
expect(vm.isLoading).toBe(true);
- vm.$nextTick(() => {
+ setTimeout(() => {
expect(vm.isLoading).toBe(false);
done();
diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js
index 7ddc734ff56..1e5b55af4ba 100644
--- a/spec/javascripts/ide/stores/actions/file_spec.js
+++ b/spec/javascripts/ide/stores/actions/file_spec.js
@@ -121,68 +121,48 @@ describe('IDE store file actions', () => {
store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
});
- it('calls scrollToTab', done => {
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(scrollToTabSpy).toHaveBeenCalled();
-
- done();
- })
- .catch(done.fail);
- });
+ it('calls scrollToTab', () => {
+ const dispatch = jasmine.createSpy();
- it('sets the file active', done => {
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(localFile.active).toBeTruthy();
+ actions.setFileActive(
+ { commit() {}, state: store.state, getters: store.getters, dispatch },
+ localFile.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(dispatch).toHaveBeenCalledWith('scrollToTab');
});
- it('returns early if file is already active', done => {
- localFile.active = true;
+ it('commits SET_FILE_ACTIVE', () => {
+ const commit = jasmine.createSpy();
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(scrollToTabSpy).not.toHaveBeenCalled();
+ actions.setFileActive(
+ { commit, state: store.state, getters: store.getters, dispatch() {} },
+ localFile.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', {
+ path: localFile.path,
+ active: true,
+ });
});
- it('sets current active file to not active', done => {
+ it('sets current active file to not active', () => {
const f = file('newActive');
store.state.entries[f.path] = f;
localFile.active = true;
store.state.openFiles.push(localFile);
- store
- .dispatch('setFileActive', f.path)
- .then(() => {
- expect(localFile.active).toBeFalsy();
+ const commit = jasmine.createSpy();
- done();
- })
- .catch(done.fail);
- });
-
- it('resets location.hash for line highlighting', done => {
- window.location.hash = 'test';
-
- store
- .dispatch('setFileActive', localFile.path)
- .then(() => {
- expect(window.location.hash).not.toBe('test');
+ actions.setFileActive(
+ { commit, state: store.state, getters: store.getters, dispatch() {} },
+ f.path,
+ );
- done();
- })
- .catch(done.fail);
+ expect(commit).toHaveBeenCalledWith('SET_FILE_ACTIVE', {
+ path: localFile.path,
+ active: false,
+ });
});
});
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index 4f4c9a7b463..069e2cb07b5 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -4,7 +4,7 @@ import axios from '~/lib/utils/axios_utils';
import IntegrationSettingsForm from '~/integrations/integration_settings_form';
describe('IntegrationSettingsForm', () => {
- const FIXTURE = 'services/edit_service.html.raw';
+ const FIXTURE = 'services/edit_service.html';
preloadFixtures(FIXTURE);
beforeEach(() => {
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 0ccf771c7ef..dfc889773c1 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -75,7 +75,7 @@ describe('Issuable output', () => {
.then(() => {
expect(document.querySelector('title').innerText).toContain('this is a title (#1)');
expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>this is a title</p>');
- expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>this is a description!</p>');
+ expect(vm.$el.querySelector('.md').innerHTML).toContain('<p>this is a description!</p>');
expect(vm.$el.querySelector('.js-task-list-field').value).toContain(
'this is a description',
);
@@ -92,7 +92,7 @@ describe('Issuable output', () => {
.then(() => {
expect(document.querySelector('title').innerText).toContain('2 (#1)');
expect(vm.$el.querySelector('.title').innerHTML).toContain('<p>2</p>');
- expect(vm.$el.querySelector('.wiki').innerHTML).toContain('<p>42</p>');
+ expect(vm.$el.querySelector('.md').innerHTML).toContain('<p>42</p>');
expect(vm.$el.querySelector('.js-task-list-field').value).toContain('42');
expect(vm.$el.querySelector('.edited-text')).toBeTruthy();
expect(formatText(vm.$el.querySelector('.edited-text').innerText)).toMatch(
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 2eeed6770be..7e00fbf2745 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -43,12 +43,12 @@ describe('Description component', () => {
Vue.nextTick(() => {
expect(
- vm.$el.querySelector('.wiki').classList.contains('issue-realtime-pre-pulse'),
+ vm.$el.querySelector('.md').classList.contains('issue-realtime-pre-pulse'),
).toBeTruthy();
setTimeout(() => {
expect(
- vm.$el.querySelector('.wiki').classList.contains('issue-realtime-trigger-pulse'),
+ vm.$el.querySelector('.md').classList.contains('issue-realtime-trigger-pulse'),
).toBeTruthy();
done();
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index 7be495d1d35..11ab6c38a55 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -9,9 +9,9 @@ import '~/lib/utils/text_utility';
describe('Issue', function() {
let $boxClosed, $boxOpen, $btn;
- preloadFixtures('issues/closed-issue.html.raw');
- preloadFixtures('issues/issue-with-task-list.html.raw');
- preloadFixtures('issues/open-issue.html.raw');
+ preloadFixtures('issues/closed-issue.html');
+ preloadFixtures('issues/issue-with-task-list.html');
+ preloadFixtures('issues/open-issue.html');
function expectErrorMessage() {
const $flashMessage = $('div.flash-alert');
@@ -105,9 +105,9 @@ describe('Issue', function() {
beforeEach(function() {
if (isIssueInitiallyOpen) {
- loadFixtures('issues/open-issue.html.raw');
+ loadFixtures('issues/open-issue.html');
} else {
- loadFixtures('issues/closed-issue.html.raw');
+ loadFixtures('issues/closed-issue.html');
}
mock = new MockAdapter(axios);
diff --git a/spec/javascripts/jobs/components/commit_block_spec.js b/spec/javascripts/jobs/components/commit_block_spec.js
index 98eba3ac976..c02f564d01a 100644
--- a/spec/javascripts/jobs/components/commit_block_spec.js
+++ b/spec/javascripts/jobs/components/commit_block_spec.js
@@ -9,6 +9,7 @@ describe('Commit block', () => {
const props = {
commit: {
short_id: '1f0fb84f',
+ id: '1f0fb84fb6770d74d97eee58118fd3909cd4f48c',
commit_path: 'commit/1f0fb84fb6770d74d97eee58118fd3909cd4f48c',
title: 'Update README.md',
},
@@ -42,7 +43,7 @@ describe('Commit block', () => {
it('renders clipboard button', () => {
expect(vm.$el.querySelector('button').getAttribute('data-clipboard-text')).toEqual(
- props.commit.short_id,
+ props.commit.id,
);
});
});
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js b/spec/javascripts/labels_issue_sidebar_spec.js
index e5678ee5379..ccf439aac74 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
@@ -16,10 +16,10 @@ let saveLabelCount = 0;
let mock;
describe('Issue dropdown sidebar', () => {
- preloadFixtures('static/issue_sidebar_label.html.raw');
+ preloadFixtures('static/issue_sidebar_label.html');
beforeEach(() => {
- loadFixtures('static/issue_sidebar_label.html.raw');
+ loadFixtures('static/issue_sidebar_label.html');
mock = new MockAdapter(axios);
diff --git a/spec/javascripts/lazy_loader_spec.js b/spec/javascripts/lazy_loader_spec.js
index cbdc1644430..f3fb792c62d 100644
--- a/spec/javascripts/lazy_loader_spec.js
+++ b/spec/javascripts/lazy_loader_spec.js
@@ -11,11 +11,11 @@ const execImmediately = callback => {
describe('LazyLoader', function() {
let lazyLoader = null;
- preloadFixtures('issues/issue_with_comment.html.raw');
+ preloadFixtures('issues/issue_with_comment.html');
describe('without IntersectionObserver', () => {
beforeEach(function() {
- loadFixtures('issues/issue_with_comment.html.raw');
+ loadFixtures('issues/issue_with_comment.html');
lazyLoader = new LazyLoader({
observerNode: 'foobar',
@@ -131,7 +131,7 @@ describe('LazyLoader', function() {
describe('with IntersectionObserver', () => {
beforeEach(function() {
- loadFixtures('issues/issue_with_comment.html.raw');
+ loadFixtures('issues/issue_with_comment.html');
lazyLoader = new LazyLoader({
observerNode: 'foobar',
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 0bb43c94f6a..2084c36e484 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -2,6 +2,7 @@ import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter';
import { faviconDataUrl, overlayDataUrl, faviconWithOverlayDataUrl } from './mock_data';
+import BreakpointInstance from '~/breakpoints';
const PIXEL_TOLERANCE = 0.2;
@@ -380,6 +381,38 @@ describe('common_utils', () => {
});
});
+ describe('contentTop', () => {
+ it('does not add height for fileTitle or compareVersionsHeader if screen is too small', () => {
+ spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('sm');
+
+ setFixtures(`
+ <div class="diff-file file-title-flex-parent">
+ blah blah blah
+ </div>
+ <div class="mr-version-controls">
+ more blah blah blah
+ </div>
+ `);
+
+ expect(commonUtils.contentTop()).toBe(0);
+ });
+
+ it('adds height for fileTitle and compareVersionsHeader screen is large enough', () => {
+ spyOn(BreakpointInstance, 'getBreakpointSize').and.returnValue('lg');
+
+ setFixtures(`
+ <div class="diff-file file-title-flex-parent">
+ blah blah blah
+ </div>
+ <div class="mr-version-controls">
+ more blah blah blah
+ </div>
+ `);
+
+ expect(commonUtils.contentTop()).toBe(18);
+ });
+ });
+
describe('parseBoolean', () => {
const { parseBoolean } = commonUtils;
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index 4eea364bd69..a75470b4db8 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -5,7 +5,7 @@ import LineHighlighter from '~/line_highlighter';
describe('LineHighlighter', function() {
var clickLine;
- preloadFixtures('static/line_highlighter.html.raw');
+ preloadFixtures('static/line_highlighter.html');
clickLine = function(number, eventData = {}) {
if ($.isEmptyObject(eventData)) {
return $('#L' + number).click();
@@ -15,7 +15,7 @@ describe('LineHighlighter', function() {
}
};
beforeEach(function() {
- loadFixtures('static/line_highlighter.html.raw');
+ loadFixtures('static/line_highlighter.html');
this['class'] = new LineHighlighter();
this.css = this['class'].highlightLineClass;
return (this.spies = {
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index ab809930804..431798c6ec3 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -11,9 +11,9 @@ describe('MergeRequest', function() {
describe('task lists', function() {
let mock;
- preloadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ preloadFixtures('merge_requests/merge_request_with_task_list.html');
beforeEach(function() {
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html');
spyOn(axios, 'patch').and.callThrough();
mock = new MockAdapter(axios);
@@ -125,7 +125,7 @@ describe('MergeRequest', function() {
describe('hideCloseButton', () => {
describe('merge request of another user', () => {
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html');
this.el = document.querySelector('.js-issuable-actions');
new MergeRequest(); // eslint-disable-line no-new
MergeRequest.hideCloseButton();
@@ -145,7 +145,7 @@ describe('MergeRequest', function() {
describe('merge request of current_user', () => {
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
+ loadFixtures('merge_requests/merge_request_of_current_user.html');
this.el = document.querySelector('.js-issuable-actions');
MergeRequest.hideCloseButton();
});
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index c8df05eccf5..1295d900de7 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -22,8 +22,8 @@ describe('MergeRequestTabs', function() {
};
preloadFixtures(
- 'merge_requests/merge_request_with_task_list.html.raw',
- 'merge_requests/diff_comment.html.raw',
+ 'merge_requests/merge_request_with_task_list.html',
+ 'merge_requests/diff_comment.html',
);
beforeEach(function() {
@@ -48,7 +48,7 @@ describe('MergeRequestTabs', function() {
var windowTarget = '_blank';
beforeEach(function() {
- loadFixtures('merge_requests/merge_request_with_task_list.html.raw');
+ loadFixtures('merge_requests/merge_request_with_task_list.html');
tabUrl = $('.commits-tab a').attr('href');
});
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
index 092ca9e1dab..aa4a376caf7 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
@@ -5,10 +5,10 @@ import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import timeoutPromise from './helpers/set_timeout_promise_helper';
describe('Mini Pipeline Graph Dropdown', () => {
- preloadFixtures('static/mini_dropdown_graph.html.raw');
+ preloadFixtures('static/mini_dropdown_graph.html');
beforeEach(() => {
- loadFixtures('static/mini_dropdown_graph.html.raw');
+ loadFixtures('static/mini_dropdown_graph.html');
});
describe('When is initialized', () => {
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index fb49290be19..549a7935c0f 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -203,6 +203,10 @@ describe('Area component', () => {
.length,
).toBe(data.length);
});
+
+ it('formats line width correctly', () => {
+ expect(chartData[0].lineStyle.width).toBe(2);
+ });
});
describe('scatterSeries', () => {
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index b1778029a77..454777fa912 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -33,6 +33,11 @@ describe('Dashboard', () => {
<div class="layout-page"></div>
`);
+ window.gon = {
+ ...window.gon,
+ ee: false,
+ };
+
mock = new MockAdapter(axios);
DashboardComponent = Vue.extend(Dashboard);
});
@@ -98,7 +103,7 @@ describe('Dashboard', () => {
});
});
- it('renders the dropdown with a number of environments', done => {
+ it('renders the environments dropdown with a number of environments', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
@@ -107,14 +112,16 @@ describe('Dashboard', () => {
component.store.storeEnvironmentsData(environmentData);
setTimeout(() => {
- const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul li a');
+ const dropdownMenuEnvironments = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item',
+ );
expect(dropdownMenuEnvironments.length).toEqual(component.store.environmentsData.length);
done();
});
});
- it('hides the dropdown list when there is no environments', done => {
+ it('hides the environments dropdown list when there is no environments', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
@@ -123,14 +130,16 @@ describe('Dashboard', () => {
component.store.storeEnvironmentsData([]);
setTimeout(() => {
- const dropdownMenuEnvironments = component.$el.querySelectorAll('.dropdown-menu ul');
+ const dropdownMenuEnvironments = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item',
+ );
expect(dropdownMenuEnvironments.length).toEqual(0);
done();
});
});
- it('renders the dropdown with a single is-active element', done => {
+ it('renders the environments dropdown with a single is-active element', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true, showPanels: false },
@@ -139,14 +148,31 @@ describe('Dashboard', () => {
component.store.storeEnvironmentsData(environmentData);
setTimeout(() => {
- const dropdownIsActiveElement = component.$el.querySelectorAll(
- '.dropdown-menu ul li a.is-active',
+ const dropdownItems = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item[active="true"]',
);
- expect(dropdownIsActiveElement.length).toEqual(1);
- expect(dropdownIsActiveElement[0].textContent.trim()).toEqual(
- component.currentEnvironmentName,
- );
+ expect(dropdownItems.length).toEqual(1);
+ expect(dropdownItems[0].textContent.trim()).toEqual(component.currentEnvironmentName);
+ done();
+ });
+ });
+
+ it('hides the dropdown', done => {
+ const component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ environmentsEndpoint: '',
+ },
+ });
+
+ Vue.nextTick(() => {
+ const dropdownIsActiveElement = component.$el.querySelectorAll('.environments');
+
+ expect(dropdownIsActiveElement.length).toEqual(0);
done();
});
});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index ffc7148fde2..6d4ef960c1a 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -330,6 +330,11 @@ export const metricsGroupsAPIResponse = {
weight: 1,
queries: [
{
+ appearance: {
+ line: {
+ width: 2,
+ },
+ },
query_range:
'avg(rate(container_cpu_usage_seconds_total{%{environment_filter}}[2m])) * 100',
label: 'Core Usage',
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index 1d7b885e64f..4e3140ce4f1 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -3,7 +3,7 @@ import NewBranchForm from '~/new_branch_form';
describe('Branch', function() {
describe('create a new branch', function() {
- preloadFixtures('branches/new_branch.html.raw');
+ preloadFixtures('branches/new_branch.html');
function fillNameWith(value) {
$('.js-branch-name')
@@ -16,7 +16,7 @@ describe('Branch', function() {
}
beforeEach(function() {
- loadFixtures('branches/new_branch.html.raw');
+ loadFixtures('branches/new_branch.html');
$('form').on('submit', function(e) {
return e.preventDefault();
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index d716ece3766..ef876dc2941 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -192,9 +192,9 @@ describe('note_app', () => {
expect(service.updateNote).toHaveBeenCalled();
// Wait for the requests to finish before destroying
- Vue.nextTick()
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ done();
+ });
});
});
@@ -227,9 +227,9 @@ describe('note_app', () => {
expect(service.updateNote).toHaveBeenCalled();
// Wait for the requests to finish before destroying
- Vue.nextTick()
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index c48f8188105..b632ee6736d 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -41,8 +41,6 @@ describe('issue_note_form component', () => {
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
noteId: '545',
};
-
- wrapper = createComponentWrapper();
});
afterEach(() => {
@@ -50,6 +48,10 @@ describe('issue_note_form component', () => {
});
describe('noteHash', () => {
+ beforeEach(() => {
+ wrapper = createComponentWrapper();
+ });
+
it('returns note hash string based on `noteId`', () => {
expect(wrapper.vm.noteHash).toBe(`#note_${props.noteId}`);
});
@@ -71,6 +73,10 @@ describe('issue_note_form component', () => {
});
describe('conflicts editing', () => {
+ beforeEach(() => {
+ wrapper = createComponentWrapper();
+ });
+
it('should show conflict message if note changes outside the component', done => {
wrapper.setProps({
...props,
@@ -100,6 +106,10 @@ describe('issue_note_form component', () => {
});
describe('form', () => {
+ beforeEach(() => {
+ wrapper = createComponentWrapper();
+ });
+
it('should render text area with placeholder', () => {
const textarea = wrapper.find('textarea');
@@ -198,10 +208,6 @@ describe('issue_note_form component', () => {
});
describe('with autosaveKey', () => {
- beforeEach(() => {
- wrapper.destroy();
- });
-
describe('with draft', () => {
beforeEach(done => {
Object.assign(props, {
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index 7c869d4c326..3d2c617e479 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -34,7 +34,7 @@ const htmlEscape = comment => {
describe('Notes', function() {
const FLASH_TYPE_ALERT = 'alert';
const NOTES_POST_PATH = /(.*)\/notes\?html=true$/;
- var fixture = 'snippets/show.html.raw';
+ var fixture = 'snippets/show.html';
preloadFixtures(fixture);
beforeEach(function() {
diff --git a/spec/javascripts/oauth_remember_me_spec.js b/spec/javascripts/oauth_remember_me_spec.js
index 4125706a407..381be82697e 100644
--- a/spec/javascripts/oauth_remember_me_spec.js
+++ b/spec/javascripts/oauth_remember_me_spec.js
@@ -2,10 +2,10 @@ import $ from 'jquery';
import OAuthRememberMe from '~/pages/sessions/new/oauth_remember_me';
describe('OAuthRememberMe', () => {
- preloadFixtures('static/oauth_remember_me.html.raw');
+ preloadFixtures('static/oauth_remember_me.html');
beforeEach(() => {
- loadFixtures('static/oauth_remember_me.html.raw');
+ loadFixtures('static/oauth_remember_me.html');
new OAuthRememberMe({ container: $('#oauth-container') }).bindEvents();
});
diff --git a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
index 561bd2c96cb..6a239e307e9 100644
--- a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
+++ b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
@@ -5,7 +5,7 @@ import initUserInternalRegexPlaceholder, {
} from '~/pages/admin/application_settings/account_and_limits';
describe('AccountAndLimits', () => {
- const FIXTURE = 'application_settings/accounts_and_limit.html.raw';
+ const FIXTURE = 'application_settings/accounts_and_limit.html';
let $userDefaultExternal;
let $userInternalRegex;
preloadFixtures(FIXTURE);
diff --git a/spec/javascripts/pages/admin/users/new/index_spec.js b/spec/javascripts/pages/admin/users/new/index_spec.js
index 5a849f34bc3..3896323eef7 100644
--- a/spec/javascripts/pages/admin/users/new/index_spec.js
+++ b/spec/javascripts/pages/admin/users/new/index_spec.js
@@ -2,7 +2,7 @@ import $ from 'jquery';
import UserInternalRegexHandler from '~/pages/admin/users/new/index';
describe('UserInternalRegexHandler', () => {
- const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw';
+ const FIXTURE = 'admin/users/new_with_internal_user_regex.html';
let $userExternal;
let $userEmail;
let $warningMessage;
diff --git a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js
index 7a8227479d4..1809e92e1d9 100644
--- a/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js
+++ b/spec/javascripts/pages/sessions/new/preserve_url_fragment_spec.js
@@ -2,10 +2,10 @@ import $ from 'jquery';
import preserveUrlFragment from '~/pages/sessions/new/preserve_url_fragment';
describe('preserve_url_fragment', () => {
- preloadFixtures('sessions/new.html.raw');
+ preloadFixtures('sessions/new.html');
beforeEach(() => {
- loadFixtures('sessions/new.html.raw');
+ loadFixtures('sessions/new.html');
});
it('adds the url fragment to all login and sign up form actions', () => {
diff --git a/spec/javascripts/pipelines/graph/action_component_spec.js b/spec/javascripts/pipelines/graph/action_component_spec.js
index 3d2232ff239..95717d659b8 100644
--- a/spec/javascripts/pipelines/graph/action_component_spec.js
+++ b/spec/javascripts/pipelines/graph/action_component_spec.js
@@ -55,13 +55,16 @@ describe('pipeline graph action component', () => {
component.$el.click();
- component
- .$nextTick()
- .then(() => {
- expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
- })
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ component
+ .$nextTick()
+ .then(() => {
+ expect(component.$emit).toHaveBeenCalledWith('pipelineActionRequestComplete');
+ })
+ .catch(done.fail);
+
+ done();
+ }, 0);
});
});
});
diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
index dafb892da43..3240e8e4c1b 100644
--- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js
+++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
@@ -55,7 +55,7 @@ describe('stage column component', () => {
id: 4259,
name: '<img src=x onerror=alert(document.domain)>',
status: {
- icon: 'icon_status_success',
+ icon: 'status_success',
label: 'success',
tooltip: '<img src=x onerror=alert(document.domain)>',
},
diff --git a/spec/javascripts/pipelines/stage_spec.js b/spec/javascripts/pipelines/stage_spec.js
index 3c8b8032de8..19ae7860333 100644
--- a/spec/javascripts/pipelines/stage_spec.js
+++ b/spec/javascripts/pipelines/stage_spec.js
@@ -120,13 +120,15 @@ describe('Pipelines stage component', () => {
setTimeout(() => {
component.$el.querySelector('.js-ci-action').click();
- component
- .$nextTick()
- .then(() => {
- expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
- })
- .then(done)
- .catch(done.fail);
+ setTimeout(() => {
+ component
+ .$nextTick()
+ .then(() => {
+ expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
+ })
+ .then(done)
+ .catch(done.fail);
+ }, 0);
}, 0);
});
});
diff --git a/spec/javascripts/pipelines_spec.js b/spec/javascripts/pipelines_spec.js
index 6b86f9ea437..6d4d634c575 100644
--- a/spec/javascripts/pipelines_spec.js
+++ b/spec/javascripts/pipelines_spec.js
@@ -1,10 +1,10 @@
import Pipelines from '~/pipelines';
describe('Pipelines', () => {
- preloadFixtures('static/pipeline_graph.html.raw');
+ preloadFixtures('static/pipeline_graph.html');
beforeEach(() => {
- loadFixtures('static/pipeline_graph.html.raw');
+ loadFixtures('static/pipeline_graph.html');
});
it('should be defined', () => {
diff --git a/spec/javascripts/project_select_combo_button_spec.js b/spec/javascripts/project_select_combo_button_spec.js
index 109a5000f5d..dc85292c23e 100644
--- a/spec/javascripts/project_select_combo_button_spec.js
+++ b/spec/javascripts/project_select_combo_button_spec.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import ProjectSelectComboButton from '~/project_select_combo_button';
-const fixturePath = 'static/project_select_combo_button.html.raw';
+const fixturePath = 'static/project_select_combo_button.html';
describe('Project Select Combo Button', function() {
preloadFixtures(fixturePath);
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
index 030662b4d90..1eb7cb4bd5b 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
@@ -53,36 +53,32 @@ describe('GkeProjectIdDropdown', () => {
});
it('returns default toggle text', done =>
- vm
- .$nextTick()
- .then(() => {
- vm.setItem(emptyProjectMock);
+ setTimeout(() => {
+ vm.setItem(emptyProjectMock);
- expect(vm.toggleText).toBe(LABELS.DEFAULT);
- done();
- })
- .catch(done.fail));
+ expect(vm.toggleText).toBe(LABELS.DEFAULT);
+
+ done();
+ }));
it('returns project name if project selected', done =>
- vm
- .$nextTick()
- .then(() => {
- expect(vm.toggleText).toBe(selectedProjectMock.name);
- done();
- })
- .catch(done.fail));
+ setTimeout(() => {
+ vm.isLoading = false;
+
+ expect(vm.toggleText).toBe(selectedProjectMock.name);
+
+ done();
+ }));
it('returns empty toggle text', done =>
- vm
- .$nextTick()
- .then(() => {
- vm.$store.commit(SET_PROJECTS, null);
- vm.setItem(emptyProjectMock);
+ setTimeout(() => {
+ vm.$store.commit(SET_PROJECTS, null);
+ vm.setItem(emptyProjectMock);
- expect(vm.toggleText).toBe(LABELS.EMPTY);
- done();
- })
- .catch(done.fail));
+ expect(vm.toggleText).toBe(LABELS.EMPTY);
+
+ done();
+ }));
});
describe('selectItem', () => {
diff --git a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
index 94e2f959d46..dca3e1553b9 100644
--- a/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
+++ b/spec/javascripts/prometheus_metrics/prometheus_metrics_spec.js
@@ -5,7 +5,7 @@ import PANEL_STATE from '~/prometheus_metrics/constants';
import { metrics, missingVarMetrics } from './mock_data';
describe('PrometheusMetrics', () => {
- const FIXTURE = 'services/prometheus/prometheus_service.html.raw';
+ const FIXTURE = 'services/prometheus/prometheus_service.html';
preloadFixtures(FIXTURE);
beforeEach(() => {
diff --git a/spec/javascripts/read_more_spec.js b/spec/javascripts/read_more_spec.js
index b1af0f80a50..d1d01272403 100644
--- a/spec/javascripts/read_more_spec.js
+++ b/spec/javascripts/read_more_spec.js
@@ -1,7 +1,7 @@
import initReadMore from '~/read_more';
describe('Read more click-to-expand functionality', () => {
- const fixtureName = 'projects/overview.html.raw';
+ const fixtureName = 'projects/overview.html';
preloadFixtures(fixtureName);
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index 992e17978c1..9565e3ce546 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -23,7 +23,7 @@ const assertSidebarState = function(state) {
describe('RightSidebar', function() {
describe('fixture tests', () => {
- const fixtureName = 'issues/open-issue.html.raw';
+ const fixtureName = 'issues/open-issue.html';
preloadFixtures(fixtureName);
loadJSONFixtures('todos/todos.json');
let mock;
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 7a4ca587313..ce7fa7a52ae 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -126,9 +126,9 @@ describe('Search autocomplete dropdown', () => {
expect(list.find(mrsIHaveCreatedLink).text()).toBe("Merge requests I've created");
};
- preloadFixtures('static/search_autocomplete.html.raw');
+ preloadFixtures('static/search_autocomplete.html');
beforeEach(function() {
- loadFixtures('static/search_autocomplete.html.raw');
+ loadFixtures('static/search_autocomplete.html');
window.gon = {};
window.gon.current_user_id = userId;
diff --git a/spec/javascripts/search_spec.js b/spec/javascripts/search_spec.js
index 40bdbac7451..32f60508fa3 100644
--- a/spec/javascripts/search_spec.js
+++ b/spec/javascripts/search_spec.js
@@ -3,7 +3,7 @@ import Api from '~/api';
import Search from '~/pages/search/show/search';
describe('Search', () => {
- const fixturePath = 'search/show.html.raw';
+ const fixturePath = 'search/show.html';
const searchTerm = 'some search';
const fillDropdownInput = dropdownSelector => {
const dropdownElement = document.querySelector(dropdownSelector).parentNode;
diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js
index 3b681a9ff28..2c5d91a45bc 100644
--- a/spec/javascripts/settings_panels_spec.js
+++ b/spec/javascripts/settings_panels_spec.js
@@ -2,10 +2,10 @@ import $ from 'jquery';
import initSettingsPanels from '~/settings_panels';
describe('Settings Panels', () => {
- preloadFixtures('groups/edit.html.raw');
+ preloadFixtures('groups/edit.html');
beforeEach(() => {
- loadFixtures('groups/edit.html.raw');
+ loadFixtures('groups/edit.html');
});
describe('initSettingsPane', () => {
diff --git a/spec/javascripts/shortcuts_spec.js b/spec/javascripts/shortcuts_spec.js
index 3ca6ecaa938..df7012bb659 100644
--- a/spec/javascripts/shortcuts_spec.js
+++ b/spec/javascripts/shortcuts_spec.js
@@ -2,7 +2,7 @@ import $ from 'jquery';
import Shortcuts from '~/behaviors/shortcuts/shortcuts';
describe('Shortcuts', () => {
- const fixtureName = 'snippets/show.html.raw';
+ const fixtureName = 'snippets/show.html';
const createEvent = (type, target) =>
$.Event(type, {
target,
diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js
index eced4925489..57b16b12cb0 100644
--- a/spec/javascripts/sidebar/assignees_spec.js
+++ b/spec/javascripts/sidebar/assignees_spec.js
@@ -210,6 +210,19 @@ describe('Assignee component', () => {
expect(component.$el.querySelector('.user-list-more')).toBe(null);
});
+ it('sets tooltip container to body', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(2);
+ component = new AssigneeComponent({
+ propsData: {
+ rootPath: 'http://localhost:3000',
+ users,
+ editable: true,
+ },
+ }).$mount();
+
+ expect(component.$el.querySelector('.user-link').getAttribute('data-container')).toBe('body');
+ });
+
it('Shows the "show-less" assignees label', done => {
const users = UsersMockHelper.createNumberRandomUsers(6);
component = new AssigneeComponent({
diff --git a/spec/javascripts/sidebar/sidebar_assignees_spec.js b/spec/javascripts/sidebar/sidebar_assignees_spec.js
index 3f0f67d71ca..016f5e033a5 100644
--- a/spec/javascripts/sidebar/sidebar_assignees_spec.js
+++ b/spec/javascripts/sidebar/sidebar_assignees_spec.js
@@ -11,12 +11,12 @@ describe('sidebar assignees', () => {
let vm;
let mediator;
let sidebarAssigneesEl;
- preloadFixtures('issues/open-issue.html.raw');
+ preloadFixtures('issues/open-issue.html');
beforeEach(() => {
Vue.http.interceptors.push(Mock.sidebarMockInterceptor);
- loadFixtures('issues/open-issue.html.raw');
+ loadFixtures('issues/open-issue.html');
mediator = new SidebarMediator(Mock.mediator);
spyOn(mediator, 'saveAssignees').and.callThrough();
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js b/spec/javascripts/signin_tabs_memoizer_spec.js
index 52da6a79939..ef5c774736b 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
@@ -2,7 +2,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
import SigninTabsMemoizer from '~/pages/sessions/new/signin_tabs_memoizer';
describe('SigninTabsMemoizer', () => {
- const fixtureTemplate = 'static/signin_tabs.html.raw';
+ const fixtureTemplate = 'static/signin_tabs.html';
const tabSelector = 'ul.new-session-tabs';
const currentTabKey = 'current_signin_tab';
let memo;
diff --git a/spec/javascripts/todos_spec.js b/spec/javascripts/todos_spec.js
index 69e43274250..802f54f6a7e 100644
--- a/spec/javascripts/todos_spec.js
+++ b/spec/javascripts/todos_spec.js
@@ -3,11 +3,11 @@ import Todos from '~/pages/dashboard/todos/index/todos';
import '~/lib/utils/common_utils';
describe('Todos', () => {
- preloadFixtures('todos/todos.html.raw');
+ preloadFixtures('todos/todos.html');
let todoItem;
beforeEach(() => {
- loadFixtures('todos/todos.html.raw');
+ loadFixtures('todos/todos.html');
todoItem = document.querySelector('.todos-list .todo');
return new Todos();
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index ddb09811dda..8f9cb270729 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -4,10 +4,10 @@ import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
describe('U2FAuthenticate', function() {
- preloadFixtures('u2f/authenticate.html.raw');
+ preloadFixtures('u2f/authenticate.html');
beforeEach(() => {
- loadFixtures('u2f/authenticate.html.raw');
+ loadFixtures('u2f/authenticate.html');
this.u2fDevice = new MockU2FDevice();
this.container = $('#js-authenticate-u2f');
this.component = new U2FAuthenticate(
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index 261db3d66d7..a75ceca9f4c 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -4,10 +4,10 @@ import 'vendor/u2f';
import MockU2FDevice from './mock_u2f_device';
describe('U2FRegister', function() {
- preloadFixtures('u2f/register.html.raw');
+ preloadFixtures('u2f/register.html');
beforeEach(done => {
- loadFixtures('u2f/register.html.raw');
+ loadFixtures('u2f/register.html');
this.u2fDevice = new MockU2FDevice();
this.container = $('#js-register-u2f');
this.component = new U2FRegister(this.container, $('#js-register-u2f-templates'), {}, 'token');
diff --git a/spec/javascripts/user_popovers_spec.js b/spec/javascripts/user_popovers_spec.js
index b174a51c1a0..c0d5ee9c446 100644
--- a/spec/javascripts/user_popovers_spec.js
+++ b/spec/javascripts/user_popovers_spec.js
@@ -2,7 +2,7 @@ import initUserPopovers from '~/user_popovers';
import UsersCache from '~/lib/utils/users_cache';
describe('User Popovers', () => {
- const fixtureTemplate = 'merge_requests/diff_comment.html.raw';
+ const fixtureTemplate = 'merge_requests/diff_comment.html';
preloadFixtures(fixtureTemplate);
const selector = '.js-user-link';
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
index 3229ddd5e27..780bed1bf69 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_failed_to_merge_spec.js
@@ -120,7 +120,7 @@ describe('MRWidgetFailedToMerge', () => {
it('renders given error', () => {
expect(vm.$el.querySelector('.has-error-message').textContent.trim()).toEqual(
- 'Merge error happened.',
+ 'Merge error happened',
);
});
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 30659ad16f3..368c997d318 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
@@ -415,7 +415,7 @@ describe('ReadyToMerge', () => {
});
beforeEach(() => {
- loadFixtures('merge_requests/merge_request_of_current_user.html.raw');
+ loadFixtures('merge_requests/merge_request_of_current_user.html');
});
it('should call start and stop polling when MR merged', done => {
diff --git a/spec/javascripts/vue_shared/components/markdown/field_spec.js b/spec/javascripts/vue_shared/components/markdown/field_spec.js
index 79e0e756a7a..02d73e1849a 100644
--- a/spec/javascripts/vue_shared/components/markdown/field_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/field_spec.js
@@ -5,7 +5,7 @@ import fieldComponent from '~/vue_shared/components/markdown/field.vue';
function assertMarkdownTabs(isWrite, writeLink, previewLink, vm) {
expect(writeLink.parentNode.classList.contains('active')).toEqual(isWrite);
expect(previewLink.parentNode.classList.contains('active')).toEqual(!isWrite);
- expect(vm.$el.querySelector('.md-preview').style.display).toEqual(isWrite ? 'none' : '');
+ expect(vm.$el.querySelector('.md-preview-holder').style.display).toEqual(isWrite ? 'none' : '');
}
describe('Markdown field component', () => {
@@ -80,7 +80,9 @@ describe('Markdown field component', () => {
previewLink.click();
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.md-preview').textContent.trim()).toContain('Loading…');
+ expect(vm.$el.querySelector('.md-preview-holder').textContent.trim()).toContain(
+ 'Loading…',
+ );
done();
});
@@ -90,7 +92,7 @@ describe('Markdown field component', () => {
previewLink.click();
setTimeout(() => {
- expect(vm.$el.querySelector('.md-preview').innerHTML).toContain(
+ expect(vm.$el.querySelector('.md-preview-holder').innerHTML).toContain(
'<p>markdown preview</p>',
);
diff --git a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
index e8b41e8eeff..852558a83bc 100644
--- a/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
+++ b/spec/javascripts/vue_shared/components/user_popover/user_popover_spec.js
@@ -17,7 +17,7 @@ const DEFAULT_PROPS = {
const UserPopover = Vue.extend(userPopover);
describe('User Popover Component', () => {
- const fixtureTemplate = 'merge_requests/diff_comment.html.raw';
+ const fixtureTemplate = 'merge_requests/diff_comment.html';
preloadFixtures(fixtureTemplate);
let vm;
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index e5f1e6ae937..8f662c71c7a 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -6,7 +6,7 @@ import ZenMode from '~/zen_mode';
describe('ZenMode', () => {
let zen;
let dropzoneForElementSpy;
- const fixtureName = 'snippets/show.html.raw';
+ const fixtureName = 'snippets/show.html';
preloadFixtures(fixtureName);
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 55c41e55437..72dfd6ff9ea 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -30,6 +30,23 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
end
end
+ describe 'all references' do
+ let(:doc) { reference_filter(merge.to_reference) }
+ let(:tag_el) { doc.css('a').first }
+
+ it 'adds merge request iid' do
+ expect(tag_el["data-iid"]).to eq(merge.iid.to_s)
+ end
+
+ it 'adds project data attribute with project id' do
+ expect(tag_el["data-project-path"]).to eq(project.full_path)
+ end
+
+ it 'does not add `has-tooltip` class' do
+ expect(tag_el["class"]).not_to include('has-tooltip')
+ end
+ end
+
context 'internal reference' do
let(:reference) { merge.to_reference }
@@ -57,9 +74,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
expect(reference_filter(act).to_html).to eq exp
end
- it 'includes a title attribute' do
+ it 'has no title' do
doc = reference_filter("Merge #{reference}")
- expect(doc.css('a').first.attr('title')).to eq merge.title
+ expect(doc.css('a').first.attr('title')).to eq ""
end
it 'escapes the title attribute' do
@@ -69,9 +86,9 @@ describe Banzai::Filter::MergeRequestReferenceFilter do
expect(doc.text).to eq "Merge #{reference}"
end
- it 'includes default classes' do
+ it 'includes default classes, without tooltip' do
doc = reference_filter("Merge #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request has-tooltip'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request'
end
it 'includes a data-project attribute' do
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
new file mode 100644
index 00000000000..4a81a37d341
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_assignees_table_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::BackgroundMigration::PopulateMergeRequestAssigneesTable, :migration, schema: 20190315191339 do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:users) { table(:users) }
+
+ let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') }
+ let(:user_2) { users.create!(email: 'test2@example.com', projects_limit: 100, username: 'test') }
+ let(:user_3) { users.create!(email: 'test3@example.com', projects_limit: 100, username: 'test') }
+
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+ let(:merge_request_assignees) { table(:merge_request_assignees) }
+
+ def create_merge_request(id, params = {})
+ params.merge!(id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}")
+
+ merge_requests.create(params)
+ 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') }
+ existing_rows = [
+ { 'merge_request_id' => 2, 'user_id' => user_3.id }
+ ]
+ created_rows = [
+ { 'merge_request_id' => 3, 'user_id' => user_2.id },
+ { 'merge_request_id' => 4, 'user_id' => user_3.id }
+ ]
+ expected_rows = existing_rows + created_rows
+
+ expect(rows.size).to eq(expected_rows.size)
+ expected_rows.each do |expected_row|
+ expect(rows).to include(expected_row)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index dab0fb51bcc..5181e9c1583 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -48,6 +48,24 @@ describe Gitlab::Ci::Pipeline::Chain::Command do
end
end
+ describe '#merge_request_ref_exists?' do
+ subject { command.merge_request_ref_exists? }
+
+ let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+
+ context 'for existing merge request ref' do
+ let(:origin_ref) { merge_request.ref_path }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'for branch ref' do
+ let(:origin_ref) { merge_request.source_branch }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
describe '#ref' do
subject { command.ref }
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 8ba56d73838..7d750877d09 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -10,12 +10,33 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
let(:command) do
Gitlab::Ci::Pipeline::Chain::Command.new(
- project: project, current_user: user, origin_ref: ref)
+ project: project, current_user: user, origin_ref: origin_ref, merge_request: merge_request)
end
let(:step) { described_class.new(pipeline, command) }
let(:ref) { 'master' }
+ let(:origin_ref) { ref }
+ let(:merge_request) { nil }
+
+ shared_context 'detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref,
+ target_project: project,
+ target_branch: 'feature')
+ end
+
+ let(:pipeline) do
+ build(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: project)
+ end
+
+ let(:origin_ref) { merge_request.ref_path }
+ end
context 'when users has no ability to run a pipeline' do
before do
@@ -58,6 +79,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
it { is_expected.to be_truthy }
+ context 'when pipeline is a detached merge request pipeline' do
+ include_context 'detached merge request pipeline'
+
+ it { is_expected.to be_truthy }
+ end
+
context 'when the branch is protected' do
let!(:protected_branch) do
create(:protected_branch, project: project, name: ref)
@@ -65,6 +92,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
it { is_expected.to be_falsey }
+ context 'when pipeline is a detached merge request pipeline' do
+ include_context 'detached merge request pipeline'
+
+ it { is_expected.to be_falsey }
+ end
+
context 'when developers are allowed to merge' do
let!(:protected_branch) do
create(:protected_branch,
@@ -74,6 +107,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
end
it { is_expected.to be_truthy }
+
+ context 'when pipeline is a detached merge request pipeline' do
+ include_context 'detached merge request pipeline'
+
+ it { is_expected.to be_truthy }
+ end
end
end
@@ -112,6 +151,12 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
end
it { is_expected.to be_truthy }
+
+ context 'when pipeline is a detached merge request pipeline' do
+ include_context 'detached merge request pipeline'
+
+ it { is_expected.to be_truthy }
+ end
end
context 'when the tag is protected' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
index a7cad423d09..2e8c9d70098 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
@@ -42,6 +42,25 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
end
end
+ context 'when origin ref is a merge request ref' do
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ project: project, current_user: user, origin_ref: origin_ref, checkout_sha: checkout_sha)
+ end
+
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:origin_ref) { merge_request.ref_path }
+ let(:checkout_sha) { project.repository.commit(merge_request.ref_path).id }
+
+ it 'does not break the chain' do
+ expect(step.break?).to be false
+ end
+
+ it 'does not append pipeline errors' do
+ expect(pipeline.errors).to be_empty
+ end
+ end
+
context 'when ref is ambiguous' do
let(:project) do
create(:project, :repository).tap do |proj|
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index 17d5eae24f5..909dbffa38f 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -115,9 +115,8 @@ describe Gitlab::CurrentSettings do
shared_examples 'a non-persisted ApplicationSetting object' do
let(:current_settings) { described_class.current_application_settings }
- it 'returns a non-persisted ApplicationSetting object' do
- expect(current_settings).to be_a(ApplicationSetting)
- expect(current_settings).not_to be_persisted
+ it 'returns a FakeApplicationSettings object' do
+ expect(current_settings).to be_a(Gitlab::FakeApplicationSettings)
end
it 'uses the default value from ApplicationSetting.defaults' do
@@ -146,6 +145,16 @@ describe Gitlab::CurrentSettings do
it 'uses the value from the DB attribute if present and not overridden by an accessor' do
expect(current_settings.home_page_url).to eq(db_settings.home_page_url)
end
+
+ context 'when a new column is used before being migrated' do
+ before do
+ allow(ApplicationSetting).to receive(:defaults).and_return({ foo: 'bar' })
+ end
+
+ it 'uses the default value if present' do
+ expect(current_settings.foo).to eq('bar')
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/diff/suggestion_spec.rb b/spec/lib/gitlab/diff/suggestion_spec.rb
new file mode 100644
index 00000000000..71fd25df698
--- /dev/null
+++ b/spec/lib/gitlab/diff/suggestion_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::Suggestion do
+ shared_examples 'correct suggestion raw content' do
+ it 'returns correct raw data' do
+ expect(suggestion.to_hash).to include(from_content: expected_lines.join,
+ to_content: "#{text}\n",
+ lines_above: above,
+ lines_below: below)
+ end
+ end
+
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
+ let(:diff_file) do
+ position.diff_file(project.repository)
+ end
+ let(:text) { "# parsed suggestion content\n# with comments" }
+
+ def blob_lines_data(from_line, to_line)
+ diff_file.new_blob_lines_between(from_line, to_line)
+ end
+
+ def blob_data
+ blob = diff_file.new_blob
+ blob.load_all_data!
+ blob.data
+ end
+
+ let(:suggestion) do
+ described_class.new(text, line: line, above: above, below: below, diff_file: diff_file)
+ end
+
+ describe '#to_hash' do
+ context 'when changing content surpasses the top limit' do
+ let(:line) { 4 }
+ let(:above) { 5 }
+ let(:below) { 2 }
+ let(:expected_above) { line - 1 }
+ let(:expected_below) { below }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+
+ context 'when changing content surpasses the amount of lines in the blob (bottom)' do
+ let(:line) { 5 }
+ let(:above) { 1 }
+ let(:below) { blob_data.lines.size + 10 }
+ let(:expected_below) { below }
+ let(:expected_above) { above }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+
+ context 'when lines are within blob lines boundary' do
+ let(:line) { 5 }
+ let(:above) { 2 }
+ let(:below) { 3 }
+ let(:expected_below) { below }
+ let(:expected_above) { above }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+
+ context 'when no extra lines (single-line suggestion)' do
+ let(:line) { 5 }
+ let(:above) { 0 }
+ let(:below) { 0 }
+ let(:expected_below) { below }
+ let(:expected_above) { above }
+ let(:expected_lines) { blob_lines_data(line - expected_above, line + expected_below) }
+
+ it_behaves_like 'correct suggestion raw content'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/diff/suggestions_parser_spec.rb b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
new file mode 100644
index 00000000000..1119ea04995
--- /dev/null
+++ b/spec/lib/gitlab/diff/suggestions_parser_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::SuggestionsParser do
+ describe '.parse' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:position) do
+ Gitlab::Diff::Position.new(old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 9,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ let(:diff_file) do
+ position.diff_file(project.repository)
+ end
+
+ subject do
+ described_class.parse(markdown, project: merge_request.project,
+ position: position)
+ end
+
+ def blob_lines_data(from_line, to_line)
+ diff_file.new_blob_lines_between(from_line, to_line).join
+ end
+
+ context 'single-line suggestions' do
+ let(:markdown) do
+ <<-MARKDOWN.strip_heredoc
+ ```suggestion
+ foo
+ bar
+ ```
+
+ ```
+ nothing
+ ```
+
+ ```suggestion
+ xpto
+ baz
+ ```
+
+ ```thing
+ this is not a suggestion, it's a thing
+ ```
+ MARKDOWN
+ end
+
+ it 'returns a list of Gitlab::Diff::Suggestion' do
+ expect(subject).to all(be_a(Gitlab::Diff::Suggestion))
+ expect(subject.size).to eq(2)
+ end
+
+ it 'parsed suggestion has correct data' do
+ from_line, to_line = position.new_line, position.new_line
+
+ expect(subject.first.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
+ to_content: " foo\n bar\n",
+ lines_above: 0,
+ lines_below: 0)
+
+ expect(subject.second.to_hash).to include(from_content: blob_lines_data(from_line, to_line),
+ to_content: " xpto\n baz\n",
+ lines_above: 0,
+ lines_below: 0)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index 429816efec3..88ea98eb1e1 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -189,14 +189,23 @@ describe Gitlab::EncodingHelper do
end
end
- describe '#binary_stringio' do
+ describe '#binary_io' do
it 'does not mutate the original string encoding' do
test = 'my-test'
- io_stream = ext_class.binary_stringio(test)
+ io_stream = ext_class.binary_io(test)
expect(io_stream.external_encoding.name).to eq('ASCII-8BIT')
expect(test.encoding.name).to eq('UTF-8')
end
+
+ it 'returns a copy of the IO with the correct encoding' do
+ test = fixture_file_upload('spec/fixtures/doc_sample.txt').to_io
+
+ io_stream = ext_class.binary_io(test)
+
+ expect(io_stream.external_encoding.name).to eq('ASCII-8BIT')
+ expect(test).not_to eq(io_stream)
+ end
end
end
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 3fb41a626b2..4a4ac833e39 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -537,6 +537,18 @@ describe Gitlab::Git::Commit, :seed_helper do
end
end
+ describe '#gitaly_commit?' do
+ context 'when the commit data comes from gitaly' do
+ it { expect(commit.gitaly_commit?).to eq(true) }
+ end
+
+ context 'when the commit data comes from a Hash' do
+ let(:commit) { described_class.new(repository, sample_commit_hash) }
+
+ it { expect(commit.gitaly_commit?).to eq(false) }
+ end
+ end
+
describe '#has_zero_stats?' do
it { expect(commit.has_zero_stats?).to eq(false) }
end
diff --git a/spec/lib/gitlab/git/pre_receive_error_spec.rb b/spec/lib/gitlab/git/pre_receive_error_spec.rb
index 1b8be62dec6..cb030e38032 100644
--- a/spec/lib/gitlab/git/pre_receive_error_spec.rb
+++ b/spec/lib/gitlab/git/pre_receive_error_spec.rb
@@ -1,9 +1,19 @@
require 'spec_helper'
describe Gitlab::Git::PreReceiveError do
- it 'makes its message HTML-friendly' do
- ex = described_class.new("hello\nworld\n")
+ Gitlab::Git::PreReceiveError::SAFE_MESSAGE_PREFIXES.each do |prefix|
+ context "error messages prefixed with #{prefix}" do
+ it 'accepts only errors lines with the prefix' do
+ ex = described_class.new("#{prefix} Hello,\nworld!")
- expect(ex.message).to eq('hello<br>world<br>')
+ expect(ex.message).to eq('Hello,')
+ end
+
+ it 'makes its message HTML-friendly' do
+ ex = described_class.new("#{prefix} Hello,\n#{prefix} world!\n")
+
+ expect(ex.message).to eq('Hello,<br>world!')
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index d7bd757149d..6d6107ca3e7 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -221,6 +221,21 @@ describe Gitlab::GitalyClient::CommitService do
expect(commit).to eq(commit_dbl)
end
end
+
+ context 'when caching of the ref name is enabled' do
+ it 'returns a cached commit' do
+ expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit).once.and_return(double(commit: commit_dbl))
+
+ commit = nil
+ 2.times do
+ ::Gitlab::GitalyClient.allow_ref_name_caching do
+ commit = described_class.new(repository).find_commit('master')
+ end
+ end
+
+ expect(commit).to eq(commit_dbl)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
index b37fe2686b6..7579a6577b9 100644
--- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb
@@ -39,7 +39,7 @@ describe Gitlab::GitalyClient::OperationService do
context "when pre_receive_error is present" do
let(:response) do
- Gitaly::UserCreateBranchResponse.new(pre_receive_error: "something failed")
+ Gitaly::UserCreateBranchResponse.new(pre_receive_error: "GitLab: something failed")
end
it "throws a PreReceive exception" do
@@ -80,7 +80,7 @@ describe Gitlab::GitalyClient::OperationService do
context "when pre_receive_error is present" do
let(:response) do
- Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "something failed")
+ Gitaly::UserUpdateBranchResponse.new(pre_receive_error: "GitLab: something failed")
end
it "throws a PreReceive exception" do
@@ -117,7 +117,7 @@ describe Gitlab::GitalyClient::OperationService do
context "when pre_receive_error is present" do
let(:response) do
- Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "something failed")
+ Gitaly::UserDeleteBranchResponse.new(pre_receive_error: "GitLab: something failed")
end
it "throws a PreReceive exception" do
@@ -175,7 +175,7 @@ describe Gitlab::GitalyClient::OperationService do
shared_examples 'cherry pick and revert errors' do
context 'when a pre_receive_error is present' do
- let(:response) { response_class.new(pre_receive_error: "something failed") }
+ let(:response) { response_class.new(pre_receive_error: "GitLab: something failed") }
it 'raises a PreReceiveError' do
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "something failed")
@@ -313,7 +313,7 @@ describe Gitlab::GitalyClient::OperationService do
end
context 'when a pre_receive_error is present' do
- let(:response) { Gitaly::UserCommitFilesResponse.new(pre_receive_error: "something failed") }
+ let(:response) { Gitaly::UserCommitFilesResponse.new(pre_receive_error: "GitLab: something failed") }
it 'raises a PreReceiveError' do
expect { subject }.to raise_error(Gitlab::Git::PreReceiveError, "something failed")
diff --git a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
index 400d426c949..0dab39575b9 100644
--- a/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/ref_service_spec.rb
@@ -89,6 +89,16 @@ describe Gitlab::GitalyClient::RefService do
end
end
+ describe '#list_new_blobs' do
+ it 'raises DeadlineExceeded when timeout is too small' do
+ newrev = '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51'
+
+ expect do
+ client.list_new_blobs(newrev, dynamic_timeout: 0.001)
+ end.to raise_error(GRPC::DeadlineExceeded)
+ end
+ end
+
describe '#local_branches' do
it 'sends a find_local_branches message' do
expect_any_instance_of(Gitaly::RefService::Stub)
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index 680de47de2b..2e4a7c36fb8 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -11,6 +11,7 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
let(:source_commit) { project.repository.commit('feature') }
let(:target_commit) { project.repository.commit('master') }
let(:milestone) { create(:milestone, project: project) }
+ let(:state) { :closed }
let(:pull_request) do
alice = Gitlab::GithubImport::Representation::User.new(id: 4, login: 'alice')
@@ -26,13 +27,13 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
source_repository_id: 400,
target_repository_id: 200,
source_repository_owner: 'alice',
- state: :closed,
+ state: state,
milestone_number: milestone.iid,
author: alice,
assignee: alice,
created_at: created_at,
updated_at: updated_at,
- merged_at: merged_at
+ merged_at: state == :closed && merged_at
)
end
@@ -260,58 +261,63 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
end
it 'does not create the source branch if merge request is merged' do
- mr, exists = importer.create_merge_request
-
- importer.insert_git_data(mr, exists)
+ mr = insert_git_data
expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey
expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
end
- it 'creates the source branch if merge request is open' do
- mr, exists = importer.create_merge_request
- mr.state = 'opened'
- mr.save
+ context 'when merge request is open' do
+ let(:state) { :opened }
- # Ensure the project creator is creating the branches because the
- # merge request author may not have access to push to this
- # repository. The project owner may also be a group.
- allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original
+ it 'creates the source branch' do
+ # Ensure the project creator is creating the branches because the
+ # merge request author may not have access to push to this
+ # repository. The project owner may also be a group.
+ allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original
- importer.insert_git_data(mr, exists)
+ mr = insert_git_data
- expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy
- expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
- end
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ end
- it 'ignores Git errors when creating a branch' do
- mr, exists = importer.create_merge_request
- mr.state = 'opened'
- mr.save
+ it 'is able to retry on pre-receive errors' do
+ expect(importer).to receive(:insert_or_replace_git_data).twice.and_call_original
+ expect(project.repository).to receive(:add_branch).and_raise('exception')
- expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError)
- expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+ expect { insert_git_data }.to raise_error('exception')
- importer.insert_git_data(mr, exists)
+ expect(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original
- expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey
- expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ mr = insert_git_data
+
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ expect(mr.merge_request_diffs).to be_one
+ end
+
+ it 'ignores Git command errors when creating a branch' do
+ expect(project.repository).to receive(:add_branch).and_raise(Gitlab::Git::CommandError)
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
+
+ mr = insert_git_data
+
+ expect(project.repository.branch_exists?(mr.source_branch)).to be_falsey
+ expect(project.repository.branch_exists?(mr.target_branch)).to be_truthy
+ end
end
it 'creates the merge request diffs' do
- mr, exists = importer.create_merge_request
-
- importer.insert_git_data(mr, exists)
+ mr = insert_git_data
expect(mr.merge_request_diffs.exists?).to eq(true)
end
it 'creates the merge request diff commits' do
- mr, exists = importer.create_merge_request
-
- importer.insert_git_data(mr, exists)
+ mr = insert_git_data
- diff = mr.merge_request_diffs.take
+ diff = mr.merge_request_diffs.reload.first
expect(diff.merge_request_diff_commits.exists?).to eq(true)
end
@@ -327,5 +333,11 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
expect(mr.merge_request_diffs.exists?).to eq(true)
end
end
+
+ def insert_git_data
+ mr, exists = importer.create_merge_request
+ importer.insert_git_data(mr, exists)
+ mr
+ end
end
end
diff --git a/spec/lib/gitlab/gl_repository/repo_type_spec.rb b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
new file mode 100644
index 00000000000..f06a2448ff7
--- /dev/null
+++ b/spec/lib/gitlab/gl_repository/repo_type_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::GlRepository::RepoType do
+ set(:project) { create(:project) }
+
+ shared_examples 'a repo type' do
+ describe "#identifier_for_subject" do
+ subject { described_class.identifier_for_subject(project) }
+
+ it { is_expected.to eq(expected_identifier) }
+ end
+
+ describe "#fetch_id" do
+ it "finds an id match in the identifier" do
+ expect(described_class.fetch_id(expected_identifier)).to eq(expected_id)
+ end
+
+ it 'does not break on other identifiers' do
+ expect(described_class.fetch_id("wiki-noid")).to eq(nil)
+ end
+ end
+
+ describe "#path_suffix" do
+ subject { described_class.path_suffix }
+
+ it { is_expected.to eq(expected_suffix) }
+ end
+
+ describe "#repository_for" do
+ it "finds the repository for the repo type" do
+ expect(described_class.repository_for(project)).to eq(expected_repository)
+ end
+ end
+ end
+
+ describe Gitlab::GlRepository::PROJECT do
+ it_behaves_like 'a repo type' do
+ let(:expected_identifier) { "project-#{project.id}" }
+ let(:expected_id) { project.id.to_s }
+ let(:expected_suffix) { "" }
+ let(:expected_repository) { project.repository }
+ end
+
+ it "knows its type" do
+ expect(described_class).not_to be_wiki
+ expect(described_class).to be_project
+ end
+ end
+
+ describe Gitlab::GlRepository::WIKI do
+ it_behaves_like 'a repo type' do
+ let(:expected_identifier) { "wiki-#{project.id}" }
+ let(:expected_id) { project.id.to_s }
+ let(:expected_suffix) { ".wiki" }
+ let(:expected_repository) { project.wiki.repository }
+ end
+
+ it "knows its type" do
+ expect(described_class).to be_wiki
+ expect(described_class).not_to be_project
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gl_repository_spec.rb b/spec/lib/gitlab/gl_repository_spec.rb
index 4e09020471b..d4b6c629659 100644
--- a/spec/lib/gitlab/gl_repository_spec.rb
+++ b/spec/lib/gitlab/gl_repository_spec.rb
@@ -5,11 +5,11 @@ describe ::Gitlab::GlRepository do
set(:project) { create(:project, :repository) }
it 'parses a project gl_repository' do
- expect(described_class.parse("project-#{project.id}")).to eq([project, false])
+ expect(described_class.parse("project-#{project.id}")).to eq([project, Gitlab::GlRepository::PROJECT])
end
it 'parses a wiki gl_repository' do
- expect(described_class.parse("wiki-#{project.id}")).to eq([project, true])
+ expect(described_class.parse("wiki-#{project.id}")).to eq([project, Gitlab::GlRepository::WIKI])
end
it 'throws an argument error on an invalid gl_repository' do
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 01da3ea7081..e418516569a 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -100,6 +100,8 @@ merge_requests:
- head_pipeline
- latest_merge_request_diff
- merge_request_pipelines
+- merge_request_assignees
+- suggestions
merge_request_diff:
- merge_request
- merge_request_diff_commits
@@ -351,3 +353,5 @@ resource_label_events:
- label
error_tracking_setting:
- project
+suggestions:
+- note
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index ee96e5c4d42..496567b0036 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -608,3 +608,14 @@ ErrorTracking::ProjectErrorTrackingSetting:
- project_id
- project_name
- organization_name
+Suggestion:
+- id
+- note_id
+- relative_order
+- applied
+- commit_id
+- from_content
+- to_content
+- outdated
+- lines_above
+- lines_below
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index 136cfb5bcc5..b6e0adbc1c2 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -69,6 +69,36 @@ describe Gitlab::QuickActions::CommandDefinition do
expect(subject.available?(opts)).to be true
end
end
+
+ context "when the command has types" do
+ before do
+ subject.types = [Issue, Commit]
+ end
+
+ context "when the command target type is allowed" do
+ it "returns true" do
+ opts[:quick_action_target] = Issue.new
+ expect(subject.available?(opts)).to be true
+ end
+ end
+
+ context "when the command target type is not allowed" do
+ it "returns true" do
+ opts[:quick_action_target] = MergeRequest.new
+ expect(subject.available?(opts)).to be false
+ end
+ end
+ end
+
+ context "when the command has no types" do
+ it "any target type is allowed" do
+ opts[:quick_action_target] = Issue.new
+ expect(subject.available?(opts)).to be true
+
+ opts[:quick_action_target] = MergeRequest.new
+ expect(subject.available?(opts)).to be true
+ end
+ end
end
describe "#execute" do
diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
index fd4df8694ba..185adab1ff6 100644
--- a/spec/lib/gitlab/quick_actions/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -48,13 +48,19 @@ describe Gitlab::QuickActions::Dsl do
substitution :something do |text|
"#{text} Some complicated thing you want in here"
end
+
+ desc 'A command with types'
+ types Issue, Commit
+ command :has_types do
+ "Has Issue and Commit types"
+ end
end
end
describe '.command_definitions' do
it 'returns an array with commands definitions' do
no_args_def, explanation_with_aliases_def, dynamic_description_def,
- cc_def, cond_action_def, with_params_parsing_def, substitution_def =
+ cc_def, cond_action_def, with_params_parsing_def, substitution_def, has_types =
DummyClass.command_definitions
expect(no_args_def.name).to eq(:no_args)
@@ -63,6 +69,7 @@ describe Gitlab::QuickActions::Dsl do
expect(no_args_def.explanation).to eq('')
expect(no_args_def.params).to eq([])
expect(no_args_def.condition_block).to be_nil
+ expect(no_args_def.types).to eq([])
expect(no_args_def.action_block).to be_a_kind_of(Proc)
expect(no_args_def.parse_params_block).to be_nil
expect(no_args_def.warning).to eq('')
@@ -73,6 +80,7 @@ describe Gitlab::QuickActions::Dsl do
expect(explanation_with_aliases_def.explanation).to eq('Static explanation')
expect(explanation_with_aliases_def.params).to eq(['The first argument'])
expect(explanation_with_aliases_def.condition_block).to be_nil
+ expect(explanation_with_aliases_def.types).to eq([])
expect(explanation_with_aliases_def.action_block).to be_a_kind_of(Proc)
expect(explanation_with_aliases_def.parse_params_block).to be_nil
expect(explanation_with_aliases_def.warning).to eq('Possible problem!')
@@ -83,6 +91,7 @@ describe Gitlab::QuickActions::Dsl do
expect(dynamic_description_def.explanation).to eq('')
expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument'])
expect(dynamic_description_def.condition_block).to be_nil
+ expect(dynamic_description_def.types).to eq([])
expect(dynamic_description_def.action_block).to be_a_kind_of(Proc)
expect(dynamic_description_def.parse_params_block).to be_nil
expect(dynamic_description_def.warning).to eq('')
@@ -93,6 +102,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cc_def.explanation).to eq('')
expect(cc_def.params).to eq([])
expect(cc_def.condition_block).to be_nil
+ expect(cc_def.types).to eq([])
expect(cc_def.action_block).to be_nil
expect(cc_def.parse_params_block).to be_nil
expect(cc_def.warning).to eq('')
@@ -103,6 +113,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cond_action_def.explanation).to be_a_kind_of(Proc)
expect(cond_action_def.params).to eq([])
expect(cond_action_def.condition_block).to be_a_kind_of(Proc)
+ expect(cond_action_def.types).to eq([])
expect(cond_action_def.action_block).to be_a_kind_of(Proc)
expect(cond_action_def.parse_params_block).to be_nil
expect(cond_action_def.warning).to eq('')
@@ -113,6 +124,7 @@ describe Gitlab::QuickActions::Dsl do
expect(with_params_parsing_def.explanation).to eq('')
expect(with_params_parsing_def.params).to eq([])
expect(with_params_parsing_def.condition_block).to be_nil
+ expect(with_params_parsing_def.types).to eq([])
expect(with_params_parsing_def.action_block).to be_a_kind_of(Proc)
expect(with_params_parsing_def.parse_params_block).to be_a_kind_of(Proc)
expect(with_params_parsing_def.warning).to eq('')
@@ -123,9 +135,21 @@ describe Gitlab::QuickActions::Dsl do
expect(substitution_def.explanation).to eq('')
expect(substitution_def.params).to eq(['<Comment>'])
expect(substitution_def.condition_block).to be_nil
+ expect(substitution_def.types).to eq([])
expect(substitution_def.action_block.call('text')).to eq('text Some complicated thing you want in here')
expect(substitution_def.parse_params_block).to be_nil
expect(substitution_def.warning).to eq('')
+
+ expect(has_types.name).to eq(:has_types)
+ expect(has_types.aliases).to eq([])
+ expect(has_types.description).to eq('A command with types')
+ expect(has_types.explanation).to eq('')
+ expect(has_types.params).to eq([])
+ expect(has_types.condition_block).to be_nil
+ expect(has_types.types).to eq([Issue, Commit])
+ expect(has_types.action_block).to be_a_kind_of(Proc)
+ expect(has_types.parse_params_block).to be_nil
+ expect(has_types.warning).to eq('')
end
end
end
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index 13940713dfc..4c7ca4e2b57 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -6,43 +6,47 @@ describe ::Gitlab::RepoPath do
context 'a repository storage path' do
it 'parses a full repository path' do
- expect(described_class.parse(project.repository.full_path)).to eq([project, false, nil])
+ expect(described_class.parse(project.repository.full_path)).to eq([project, Gitlab::GlRepository::PROJECT, nil])
end
it 'parses a full wiki path' do
- expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, true, nil])
+ expect(described_class.parse(project.wiki.repository.full_path)).to eq([project, Gitlab::GlRepository::WIKI, nil])
end
end
context 'a relative path' do
it 'parses a relative repository path' do
- expect(described_class.parse(project.full_path + '.git')).to eq([project, false, nil])
+ expect(described_class.parse(project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil])
end
it 'parses a relative wiki path' do
- expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, true, nil])
+ expect(described_class.parse(project.full_path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, nil])
end
it 'parses a relative path starting with /' do
- expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, false, nil])
+ expect(described_class.parse('/' + project.full_path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, nil])
end
context 'of a redirected project' do
let(:redirect) { project.route.create_redirect('foo/bar') }
it 'parses a relative repository path' do
- expect(described_class.parse(redirect.path + '.git')).to eq([project, false, 'foo/bar'])
+ expect(described_class.parse(redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar'])
end
it 'parses a relative wiki path' do
- expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, true, 'foo/bar.wiki'])
+ expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, Gitlab::GlRepository::WIKI, 'foo/bar.wiki'])
end
it 'parses a relative path starting with /' do
- expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, false, 'foo/bar'])
+ expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, Gitlab::GlRepository::PROJECT, 'foo/bar'])
end
end
end
+
+ it "returns nil for non existent paths" do
+ expect(described_class.parse("path/non-existent.git")).to eq(nil)
+ end
end
describe '.find_project' do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 7213eee5675..d88086b01b1 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -250,11 +250,11 @@ describe Gitlab::Workhorse do
}
end
- subject { described_class.git_http_ok(repository, false, user, action) }
+ subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action) }
it { expect(subject).to include(params) }
- context 'when is_wiki' do
+ context 'when the repo_type is a wiki' do
let(:params) do
{
GL_ID: "user-#{user.id}",
@@ -264,7 +264,7 @@ describe Gitlab::Workhorse do
}
end
- subject { described_class.git_http_ok(repository, true, user, action) }
+ subject { described_class.git_http_ok(repository, Gitlab::GlRepository::WIKI, user, action) }
it { expect(subject).to include(params) }
end
@@ -304,7 +304,7 @@ describe Gitlab::Workhorse do
end
context 'show_all_refs enabled' do
- subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) }
+ subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) }
it { is_expected.to include(ShowAllRefs: true) }
end
@@ -322,7 +322,7 @@ describe Gitlab::Workhorse do
it { expect(subject).to include(gitaly_params) }
context 'show_all_refs enabled' do
- subject { described_class.git_http_ok(repository, false, user, action, show_all_refs: true) }
+ subject { described_class.git_http_ok(repository, Gitlab::GlRepository::PROJECT, user, action, show_all_refs: true) }
it { is_expected.to include(ShowAllRefs: true) }
end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 8232715d00e..767b5779a79 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -10,7 +10,7 @@ describe Gitlab do
end
describe '.revision' do
- let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] }
+ let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h --abbrev=11 -n 1] }
around do |example|
described_class.instance_variable_set(:@_revision, nil)
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index 88e7e2e5ebb..3333f8307ae 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -65,7 +65,9 @@ describe Sentry::Client do
let(:issue_status) { 'unresolved' }
let(:limit) { 20 }
- let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: issues_sample_response) }
+ let(:sentry_api_response) { issues_sample_response }
+
+ let!(:sentry_api_request) { stub_sentry_request(sentry_url + '/issues/?limit=20&query=is:unresolved', body: sentry_api_response) }
subject { client.list_issues(issue_status: issue_status, limit: limit) }
@@ -74,6 +76,14 @@ describe Sentry::Client do
it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error
it_behaves_like 'has correct length', 1
+ shared_examples 'has correct external_url' do
+ context 'external_url' do
+ it 'is constructed correctly' do
+ expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11')
+ end
+ end
+ end
+
context 'error object created from sentry response' do
using RSpec::Parameterized::TableSyntax
@@ -96,14 +106,10 @@ describe Sentry::Client do
end
with_them do
- it { expect(subject[0].public_send(error_object)).to eq(issues_sample_response[0].dig(*sentry_response)) }
+ it { expect(subject[0].public_send(error_object)).to eq(sentry_api_response[0].dig(*sentry_response)) }
end
- context 'external_url' do
- it 'is constructed correctly' do
- expect(subject[0].external_url).to eq('https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/11')
- end
- end
+ it_behaves_like 'has correct external_url'
end
context 'redirects' do
@@ -135,12 +141,42 @@ describe Sentry::Client do
expect(valid_req_stub).to have_been_requested
end
end
+
+ context 'Older sentry versions where keys are not present' do
+ let(:sentry_api_response) do
+ issues_sample_response[0...1].map do |issue|
+ issue[:project].delete(:id)
+ issue
+ end
+ end
+
+ it_behaves_like 'calls sentry api'
+
+ it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Error
+ it_behaves_like 'has correct length', 1
+
+ it_behaves_like 'has correct external_url'
+ end
+
+ context 'essential keys missing in API response' do
+ let(:sentry_api_response) do
+ issues_sample_response[0...1].map do |issue|
+ issue.except(:id)
+ end
+ end
+
+ it 'raises exception' do
+ expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"')
+ end
+ end
end
describe '#list_projects' do
let(:sentry_list_projects_url) { 'https://sentrytest.gitlab.com/api/0/projects/' }
- let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: projects_sample_response) }
+ let(:sentry_api_response) { projects_sample_response }
+
+ let!(:sentry_api_request) { stub_sentry_request(sentry_list_projects_url, body: sentry_api_response) }
subject { client.list_projects }
@@ -149,14 +185,31 @@ describe Sentry::Client do
it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project
it_behaves_like 'has correct length', 2
- context 'keys missing in API response' do
- it 'raises exception' do
- projects_sample_response[0].delete(:slug)
+ context 'essential keys missing in API response' do
+ let(:sentry_api_response) do
+ projects_sample_response[0...1].map do |project|
+ project.except(:slug)
+ end
+ end
- stub_sentry_request(sentry_list_projects_url, body: projects_sample_response)
+ it 'raises exception' do
+ expect { subject }.to raise_error(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "slug"')
+ end
+ end
- expect { subject }.to raise_error(Sentry::Client::SentryError, 'Sentry API response is missing keys. key not found: "slug"')
+ context 'optional keys missing in sentry response' do
+ let(:sentry_api_response) do
+ projects_sample_response[0...1].map do |project|
+ project[:organization].delete(:id)
+ project.delete(:id)
+ project.except(:status)
+ end
end
+
+ it_behaves_like 'calls sentry api'
+
+ it_behaves_like 'has correct return type', Gitlab::ErrorTracking::Project
+ it_behaves_like 'has correct length', 1
end
context 'error object created from sentry response' do
@@ -173,7 +226,11 @@ describe Sentry::Client do
end
with_them do
- it { expect(subject[0].public_send(sentry_project_object)).to eq(projects_sample_response[0].dig(*sentry_response)) }
+ it do
+ expect(subject[0].public_send(sentry_project_object)).to(
+ eq(sentry_api_response[0].dig(*sentry_response))
+ )
+ end
end
end
diff --git a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb
index b2d8f476bb2..a1f243651b5 100644
--- a/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_cluster_configure_worker_sidekiq_queue_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20181219145520_migrate_cluster_co
describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queue' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_platform_configure').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
described_class.new.up
@@ -19,12 +20,12 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
it 'does not affect other queues under the same namespace' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1])
- stubbed_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_install_app').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_provision').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_wait_for_app_installation').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:wait_for_cluster_creation').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_wait_for_ingress_ip_address').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_project_configure').perform_async('Something', [1])
described_class.new.up
@@ -39,7 +40,7 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
+ stub_worker(queue: 'gcp_cluster:cluster_configure').perform_async('Something', [1])
described_class.new.down
@@ -58,11 +59,4 @@ describe MigrateClusterConfigureWorkerSidekiqQueue, :sidekiq, :redis do
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
index c18ae3b76d3..66555118a43 100644
--- a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_tra
describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queues' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
described_class.new.up
@@ -19,11 +20,11 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
it 'does not affect other queues under the same namespace' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
described_class.new.up
@@ -37,7 +38,7 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
described_class.new.down
@@ -56,11 +57,4 @@ describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb
index 1ee6c440cf4..6ce04805e5d 100644
--- a/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_object_storage_upload_sidekiq_queue_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180603190921_migrate_object_sto
describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queue' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'object_storage_upload').perform_async('Something', [1])
- stubbed_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1])
+ stub_worker(queue: 'object_storage_upload').perform_async('Something', [1])
+ stub_worker(queue: 'object_storage:object_storage_background_move').perform_async('Something', [1])
described_class.new.up
@@ -23,11 +24,4 @@ describe MigrateObjectStorageUploadSidekiqQueue, :sidekiq, :redis do
expect { described_class.new.up }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb
index e02bcd2f4da..e38044ccceb 100644
--- a/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb
+++ b/spec/migrations/migrate_pipeline_sidekiq_queues_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20170822101017_migrate_pipeline_s
describe MigratePipelineSidekiqQueues, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queues' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: :pipeline).perform_async('Something', [1])
- stubbed_worker(queue: :build).perform_async('Something', [1])
+ stub_worker(queue: :pipeline).perform_async('Something', [1])
+ stub_worker(queue: :build).perform_async('Something', [1])
described_class.new.up
@@ -20,10 +21,10 @@ describe MigratePipelineSidekiqQueues, :sidekiq, :redis do
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: :pipeline_default).perform_async('Class', [1])
- stubbed_worker(queue: :pipeline_processing).perform_async('Class', [2])
- stubbed_worker(queue: :pipeline_hooks).perform_async('Class', [3])
- stubbed_worker(queue: :pipeline_cache).perform_async('Class', [4])
+ stub_worker(queue: :pipeline_default).perform_async('Class', [1])
+ stub_worker(queue: :pipeline_processing).perform_async('Class', [2])
+ stub_worker(queue: :pipeline_hooks).perform_async('Class', [3])
+ stub_worker(queue: :pipeline_cache).perform_async('Class', [4])
described_class.new.down
@@ -45,11 +46,4 @@ describe MigratePipelineSidekiqQueues, :sidekiq, :redis do
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
index f8cf76cb339..94de208e53e 100644
--- a/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_storage_migrator_sidekiq_queue_spec.rb
@@ -3,11 +3,12 @@ require Rails.root.join('db', 'post_migrate', '20190124200344_migrate_storage_mi
describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queues' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: :storage_migrator).perform_async(1, 5)
+ stub_worker(queue: :storage_migrator).perform_async(1, 5)
described_class.new.up
@@ -18,7 +19,7 @@ describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: :'hashed_storage:hashed_storage_migrator').perform_async(1, 5)
+ stub_worker(queue: :'hashed_storage:hashed_storage_migrator').perform_async(1, 5)
described_class.new.down
@@ -37,11 +38,4 @@ describe MigrateStorageMigratorSidekiqQueue, :sidekiq, :redis do
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
index 5e3b20ab4a8..976f3ce07d7 100644
--- a/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
+++ b/spec/migrations/migrate_update_head_pipeline_for_merge_request_sidekiq_queue_spec.rb
@@ -3,12 +3,13 @@ require Rails.root.join('db', 'post_migrate', '20180307012445_migrate_update_hea
describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis do
include Gitlab::Database::MigrationHelpers
+ include StubWorker
context 'when there are jobs in the queues' do
it 'correctly migrates queue when migrating up' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
described_class.new.up
@@ -19,10 +20,10 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis
it 'does not affect other queues under the same namespace' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
- stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
described_class.new.up
@@ -35,7 +36,7 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis
it 'correctly migrates queue when migrating down' do
Sidekiq::Testing.disable! do
- stubbed_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+ stub_worker(queue: 'pipeline_processing:update_head_pipeline_for_merge_request').perform_async('Something', [1])
described_class.new.down
@@ -54,11 +55,4 @@ describe MigrateUpdateHeadPipelineForMergeRequestSidekiqQueue, :sidekiq, :redis
expect { described_class.new.down }.not_to raise_error
end
end
-
- def stubbed_worker(queue:)
- Class.new do
- include Sidekiq::Worker
- sidekiq_options queue: queue
- end
- end
end
diff --git a/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
new file mode 100644
index 00000000000..e397fbb7138
--- /dev/null
+++ b/spec/migrations/schedule_populate_merge_request_assignees_table_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190322132835_schedule_populate_merge_request_assignees_table.rb')
+
+describe SchedulePopulateMergeRequestAssigneesTable, :migration, :sidekiq do
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') }
+ let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') }
+ let(:merge_requests) { table(:merge_requests) }
+
+ def create_merge_request(id)
+ params = {
+ id: id,
+ target_project_id: project.id,
+ target_branch: 'master',
+ source_project_id: project.id,
+ source_branch: 'mr name',
+ title: "mr name#{id}"
+ }
+
+ merge_requests.create!(params)
+ end
+
+ it 'correctly schedules background migrations' do
+ create_merge_request(1)
+ create_merge_request(2)
+ create_merge_request(3)
+
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(8.minutes, 1, 2)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(16.minutes, 3, 3)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index eee80e9bad7..d9d60e02a97 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ability do
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index f49a61062c1..a5f8e999d5d 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe AbuseReport do
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index e128fe8a4b7..b523f393ece 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb
index 3e95aa2b5dd..209d138f956 100644
--- a/spec/models/appearance_spec.rb
+++ b/spec/models/appearance_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Appearance do
diff --git a/spec/models/application_setting/term_spec.rb b/spec/models/application_setting/term_spec.rb
index aa49594f4d1..dd263335b81 100644
--- a/spec/models/application_setting/term_spec.rb
+++ b/spec/models/application_setting/term_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ApplicationSetting::Term do
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 314f0728b8e..c5579dafb4a 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ApplicationSetting do
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
index 3f52091698c..8452ac69734 100644
--- a/spec/models/award_emoji_spec.rb
+++ b/spec/models/award_emoji_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AwardEmoji do
diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb
index 33dc19e3432..314d7d1e9f4 100644
--- a/spec/models/badge_spec.rb
+++ b/spec/models/badge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Badge do
diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb
index ed7f83d0489..c297bc957ea 100644
--- a/spec/models/badges/group_badge_spec.rb
+++ b/spec/models/badges/group_badge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupBadge do
diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb
index 0e1a8159cb6..e683d110252 100644
--- a/spec/models/badges/project_badge_spec.rb
+++ b/spec/models/badges/project_badge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectBadge do
diff --git a/spec/models/blob_spec.rb b/spec/models/blob_spec.rb
index 05cf242e84d..d0e1688cce3 100644
--- a/spec/models/blob_spec.rb
+++ b/spec/models/blob_spec.rb
@@ -1,4 +1,6 @@
# encoding: utf-8
+# frozen_string_literal: true
+
require 'rails_helper'
describe Blob do
diff --git a/spec/models/blob_viewer/base_spec.rb b/spec/models/blob_viewer/base_spec.rb
index 7ba28f72215..39c7a34f052 100644
--- a/spec/models/blob_viewer/base_spec.rb
+++ b/spec/models/blob_viewer/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Base do
diff --git a/spec/models/blob_viewer/changelog_spec.rb b/spec/models/blob_viewer/changelog_spec.rb
index db41eca0fc8..0fcc94182af 100644
--- a/spec/models/blob_viewer/changelog_spec.rb
+++ b/spec/models/blob_viewer/changelog_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Changelog do
diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb
index 85b0d9668a0..eda34779679 100644
--- a/spec/models/blob_viewer/composer_json_spec.rb
+++ b/spec/models/blob_viewer/composer_json_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::ComposerJson do
diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb
index d8c4490637f..b6cc82c03ba 100644
--- a/spec/models/blob_viewer/gemspec_spec.rb
+++ b/spec/models/blob_viewer/gemspec_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Gemspec do
diff --git a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
index 16bf947b493..db405ceb4f1 100644
--- a/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
+++ b/spec/models/blob_viewer/gitlab_ci_yml_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::GitlabCiYml do
diff --git a/spec/models/blob_viewer/license_spec.rb b/spec/models/blob_viewer/license_spec.rb
index 222ed166ee0..e02bfae3829 100644
--- a/spec/models/blob_viewer/license_spec.rb
+++ b/spec/models/blob_viewer/license_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::License do
diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb
index fbaa8d47a71..b317278f3c8 100644
--- a/spec/models/blob_viewer/package_json_spec.rb
+++ b/spec/models/blob_viewer/package_json_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::PackageJson do
diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb
index 9a23877b23f..7f1fb8666fd 100644
--- a/spec/models/blob_viewer/podspec_json_spec.rb
+++ b/spec/models/blob_viewer/podspec_json_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::PodspecJson do
diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb
index 02d06ea24d6..527ae79d766 100644
--- a/spec/models/blob_viewer/podspec_spec.rb
+++ b/spec/models/blob_viewer/podspec_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Podspec do
diff --git a/spec/models/blob_viewer/readme_spec.rb b/spec/models/blob_viewer/readme_spec.rb
index 8d11d58cfca..958927bddb4 100644
--- a/spec/models/blob_viewer/readme_spec.rb
+++ b/spec/models/blob_viewer/readme_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::Readme do
diff --git a/spec/models/blob_viewer/route_map_spec.rb b/spec/models/blob_viewer/route_map_spec.rb
index c13662427b0..f7ce873c9d1 100644
--- a/spec/models/blob_viewer/route_map_spec.rb
+++ b/spec/models/blob_viewer/route_map_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::RouteMap do
diff --git a/spec/models/blob_viewer/server_side_spec.rb b/spec/models/blob_viewer/server_side_spec.rb
index 63790486200..f95305abe78 100644
--- a/spec/models/blob_viewer/server_side_spec.rb
+++ b/spec/models/blob_viewer/server_side_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BlobViewer::ServerSide do
diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb
index 12d29540137..54452faa0e1 100644
--- a/spec/models/board_spec.rb
+++ b/spec/models/board_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Board do
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 30ca07d5d2c..3ab013ddc0e 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BroadcastMessage do
diff --git a/spec/models/chat_name_spec.rb b/spec/models/chat_name_spec.rb
index 504bc710b25..82991937644 100644
--- a/spec/models/chat_name_spec.rb
+++ b/spec/models/chat_name_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatName do
diff --git a/spec/models/chat_team_spec.rb b/spec/models/chat_team_spec.rb
index 70a9a206faa..76beb3d506b 100644
--- a/spec/models/chat_team_spec.rb
+++ b/spec/models/chat_team_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatTeam do
diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb
index 0014bbcf9f5..f63816fd92a 100644
--- a/spec/models/ci/artifact_blob_spec.rb
+++ b/spec/models/ci/artifact_blob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::ArtifactBlob do
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index b5ec8991720..aacfbe3f180 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Bridge do
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
index 016a5899eef..917a65ddf21 100644
--- a/spec/models/ci/build_metadata_spec.rb
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildMetadata do
diff --git a/spec/models/ci/build_runner_session_spec.rb b/spec/models/ci/build_runner_session_spec.rb
index 35622366829..a52c10019e6 100644
--- a/spec/models/ci/build_runner_session_spec.rb
+++ b/spec/models/ci/build_runner_session_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildRunnerSession, model: true do
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 7500e6ae5b1..2c41dfa65cd 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Build do
@@ -24,6 +26,8 @@ describe Ci::Build do
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
it { is_expected.to delegate_method(:merge_request_event?).to(:pipeline) }
+ it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) }
+ it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) }
it { is_expected.to be_a(ArtifactMigratable) }
@@ -3626,6 +3630,24 @@ describe Ci::Build do
it { is_expected.to be_falsey }
end
end
+
+ context 'when refspecs feature is required by build' do
+ before do
+ allow(build).to receive(:merge_request_ref?) { true }
+ end
+
+ context 'when runner provides given feature' do
+ let(:runner_features) { { refspecs: true } }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when runner does not provide given feature' do
+ let(:runner_features) { {} }
+
+ it { is_expected.to be_falsey }
+ end
+ end
end
describe '#deployment_status' do
diff --git a/spec/models/ci/build_trace_chunks/database_spec.rb b/spec/models/ci/build_trace_chunks/database_spec.rb
index d8fc9d57e95..eb94d7dae38 100644
--- a/spec/models/ci/build_trace_chunks/database_spec.rb
+++ b/spec/models/ci/build_trace_chunks/database_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceChunks::Database do
diff --git a/spec/models/ci/build_trace_chunks/fog_spec.rb b/spec/models/ci/build_trace_chunks/fog_spec.rb
index 8f49190af13..b8d78bcd069 100644
--- a/spec/models/ci/build_trace_chunks/fog_spec.rb
+++ b/spec/models/ci/build_trace_chunks/fog_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceChunks::Fog do
diff --git a/spec/models/ci/build_trace_chunks/redis_spec.rb b/spec/models/ci/build_trace_chunks/redis_spec.rb
index 9da1e6a95ee..6cff33d24fa 100644
--- a/spec/models/ci/build_trace_chunks/redis_spec.rb
+++ b/spec/models/ci/build_trace_chunks/redis_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceChunks::Redis, :clean_gitlab_redis_shared_state do
diff --git a/spec/models/ci/build_trace_section_name_spec.rb b/spec/models/ci/build_trace_section_name_spec.rb
index 386ee6880cb..11e2d27ff79 100644
--- a/spec/models/ci/build_trace_section_name_spec.rb
+++ b/spec/models/ci/build_trace_section_name_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceSectionName, model: true do
diff --git a/spec/models/ci/build_trace_section_spec.rb b/spec/models/ci/build_trace_section_spec.rb
index 541a9a36fb8..5bd3a953ec0 100644
--- a/spec/models/ci/build_trace_section_spec.rb
+++ b/spec/models/ci/build_trace_section_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildTraceSection, model: true do
diff --git a/spec/models/ci/group_spec.rb b/spec/models/ci/group_spec.rb
index 838fa63cb1f..36c65d92840 100644
--- a/spec/models/ci/group_spec.rb
+++ b/spec/models/ci/group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Group do
diff --git a/spec/models/ci/group_variable_spec.rb b/spec/models/ci/group_variable_spec.rb
index 21d96bf3454..b3999765e5f 100644
--- a/spec/models/ci/group_variable_spec.rb
+++ b/spec/models/ci/group_variable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::GroupVariable do
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index c68ba02b8de..d7abd54eec1 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::JobArtifact do
diff --git a/spec/models/ci/legacy_stage_spec.rb b/spec/models/ci/legacy_stage_spec.rb
index 0c33c1466b7..be0307518eb 100644
--- a/spec/models/ci/legacy_stage_spec.rb
+++ b/spec/models/ci/legacy_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::LegacyStage do
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index 8ee15f0e734..81913f4a3b6 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::PipelineSchedule do
diff --git a/spec/models/ci/pipeline_schedule_variable_spec.rb b/spec/models/ci/pipeline_schedule_variable_spec.rb
index dc8427f28bc..3c9379ecb0d 100644
--- a/spec/models/ci/pipeline_schedule_variable_spec.rb
+++ b/spec/models/ci/pipeline_schedule_variable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::PipelineScheduleVariable do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 5b8097621e0..e76cf376ed1 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Pipeline, :mailer do
@@ -362,6 +364,42 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#merge_request_ref?' do
+ subject { pipeline.merge_request_ref? }
+
+ it 'calls MergeRequest#merge_request_ref?' do
+ expect(MergeRequest).to receive(:merge_request_ref?).with(pipeline.ref)
+
+ subject
+ end
+ end
+
+ describe '#legacy_detached_merge_request_pipeline?' do
+ subject { pipeline.legacy_detached_merge_request_pipeline? }
+
+ set(:merge_request) { create(:merge_request) }
+ let(:ref) { 'feature' }
+ let(:target_sha) { nil }
+
+ let(:pipeline) do
+ build(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: ref, target_sha: target_sha)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'when pipeline ref is a merge request ref' do
+ let(:ref) { 'refs/merge-requests/1/head' }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when target sha is set' do
+ let(:target_sha) { 'target-sha' }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe '#matches_sha_or_source_sha?' do
subject { pipeline.matches_sha_or_source_sha?(sample_sha) }
@@ -1460,6 +1498,14 @@ describe Ci::Pipeline, :mailer do
end
end
+ context 'with a branch name as the ref' do
+ it 'looks up commit with the full ref name' do
+ expect(pipeline.project).to receive(:commit).with('refs/heads/master').and_call_original
+
+ expect(pipeline).to be_latest
+ end
+ end
+
context 'with not latest sha' do
before do
pipeline.update(
diff --git a/spec/models/ci/pipeline_variable_spec.rb b/spec/models/ci/pipeline_variable_spec.rb
index 03d09cb31d6..2ecb688299a 100644
--- a/spec/models/ci/pipeline_variable_spec.rb
+++ b/spec/models/ci/pipeline_variable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::PipelineVariable do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index eb2daed7f32..b3ab63925dd 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Runner do
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index 3228c400155..661958390e2 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Stage, :models do
diff --git a/spec/models/ci/trigger_request_spec.rb b/spec/models/ci/trigger_request_spec.rb
index 7dcf3528f73..d04349bec92 100644
--- a/spec/models/ci/trigger_request_spec.rb
+++ b/spec/models/ci/trigger_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::TriggerRequest do
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index d4b72205203..fde8375f2a5 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Trigger do
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 02c07a2bd83..d2df6b3344e 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::Variable do
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index cf5cbf8ec5c..af7eadfc74c 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::CertManager do
@@ -9,7 +11,27 @@ describe Clusters::Applications::CertManager do
include_examples 'cluster application initial status specs'
describe '#install_command' do
- let(:cluster_issuer_file) { { "cluster_issuer.yaml": "---\napiVersion: certmanager.k8s.io/v1alpha1\nkind: ClusterIssuer\nmetadata:\n name: letsencrypt-prod\nspec:\n acme:\n server: https://acme-v02.api.letsencrypt.org/directory\n email: admin@example.com\n privateKeySecretRef:\n name: letsencrypt-prod\n http01: {}\n" } }
+ let(:cert_email) { 'admin@example.com' }
+
+ let(:cluster_issuer_file) do
+ file_contents = <<~EOF
+ ---
+ apiVersion: certmanager.k8s.io/v1alpha1
+ kind: ClusterIssuer
+ metadata:
+ name: letsencrypt-prod
+ spec:
+ acme:
+ server: https://acme-v02.api.letsencrypt.org/directory
+ email: #{cert_email}
+ privateKeySecretRef:
+ name: letsencrypt-prod
+ http01: {}
+ EOF
+
+ { "cluster_issuer.yaml": file_contents }
+ end
+
subject { cert_manager.install_command }
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::InstallCommand) }
@@ -24,9 +46,10 @@ describe Clusters::Applications::CertManager do
end
context 'for a specific user' do
+ let(:cert_email) { 'abc@xyz.com' }
+
before do
- cert_manager.email = 'abc@xyz.com'
- cluster_issuer_file[:'cluster_issuer.yaml'].gsub! 'admin@example.com', 'abc@xyz.com'
+ cert_manager.email = cert_email
end
it 'should use his/her email to register issuer with certificate provider' do
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index f16eff92167..f97d126d918 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Helm do
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index a40fa988287..09e60b9a206 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Ingress do
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 2967c4076c6..5970a1959b5 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Jupyter do
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index bf425a2617c..25493689fbc 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Knative do
@@ -107,7 +109,7 @@ describe Clusters::Applications::Knative do
subject { knative.install_command }
it 'should be initialized with latest version' do
- expect(subject.version).to eq('0.2.2')
+ expect(subject.version).to eq('0.3.0')
end
it_behaves_like 'a command'
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 81708b0c2ed..82a502addd4 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Prometheus do
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 06d9bc076cd..7e2f5835279 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Clusters::Applications::Runner do
@@ -64,24 +66,45 @@ describe Clusters::Applications::Runner do
end
context 'without a runner' do
- let(:project) { create(:project) }
- let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
let(:application) { create(:clusters_applications_runner, runner: nil, cluster: cluster) }
+ let(:runner) { application.runner }
- it 'creates a runner' do
- expect do
- subject
- end.to change { Ci::Runner.count }.by(1)
+ shared_examples 'runner creation' do
+ it 'creates a runner' do
+ expect { subject }.to change { Ci::Runner.count }.by(1)
+ end
+
+ it 'uses the new runner token' do
+ expect(values).to match(/runnerToken: '?#{runner.token}/)
+ end
end
- it 'uses the new runner token' do
- expect(values).to match(/runnerToken: '?#{application.reload.runner.token}/)
+ context 'project cluster' do
+ let(:project) { create(:project) }
+ let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
+
+ include_examples 'runner creation'
+
+ it 'creates a project runner' do
+ subject
+
+ expect(runner).to be_project_type
+ expect(runner.projects).to eq [project]
+ end
end
- it 'assigns the new runner to runner' do
- subject
+ context 'group cluster' do
+ let(:group) { create(:group) }
+ let(:cluster) { create(:cluster, :with_installed_helm, cluster_type: :group_type, groups: [group]) }
+
+ include_examples 'runner creation'
+
+ it 'creates a group runner' do
+ subject
- expect(application.reload.runner).to be_project_type
+ expect(runner).to be_group_type
+ expect(runner.groups).to eq [group]
+ end
end
end
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index acbcdc7d170..fabd2806d9a 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -620,4 +620,20 @@ describe Clusters::Cluster do
end
end
end
+
+ describe '#provided_by_user?' do
+ subject { cluster.provided_by_user? }
+
+ context 'with a GCP provider' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with an user provider' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index af65530e663..14bec17a2bd 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do
@@ -15,7 +17,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
it { is_expected.to delegate_method(:project).to(:cluster) }
it { is_expected.to delegate_method(:enabled?).to(:cluster) }
- it { is_expected.to delegate_method(:managed?).to(:cluster) }
+ it { is_expected.to delegate_method(:provided_by_user?).to(:cluster) }
it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) }
it_behaves_like 'having unique enum values'
diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb
index 82ef5a23c18..2f017e69251 100644
--- a/spec/models/clusters/project_spec.rb
+++ b/spec/models/clusters/project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Project do
diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb
index 5012e6f15c6..785db4febe0 100644
--- a/spec/models/clusters/providers/gcp_spec.rb
+++ b/spec/models/clusters/providers/gcp_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Providers::Gcp do
diff --git a/spec/models/commit_collection_spec.rb b/spec/models/commit_collection_spec.rb
index 0f5d03ff458..8a7fcb39fe2 100644
--- a/spec/models/commit_collection_spec.rb
+++ b/spec/models/commit_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CommitCollection do
@@ -37,12 +39,92 @@ describe CommitCollection do
describe '#without_merge_commits' do
it 'returns all commits except merge commits' do
+ merge_commit = project.commit("60ecb67744cb56576c30214ff52294f8ce2def98")
+ expect(merge_commit).to receive(:merge_commit?).and_return(true)
+
collection = described_class.new(project, [
- build(:commit),
- build(:commit, :merge_commit)
+ commit,
+ merge_commit
])
- expect(collection.without_merge_commits.size).to eq(1)
+ expect(collection.without_merge_commits).to contain_exactly(commit)
+ end
+ end
+
+ describe 'enrichment methods' do
+ let(:gitaly_commit) { commit }
+ let(:hash_commit) { Commit.from_hash(gitaly_commit.to_hash, project) }
+
+ describe '#unenriched' do
+ it 'returns all commits that are not backed by gitaly data' do
+ collection = described_class.new(project, [gitaly_commit, hash_commit])
+
+ expect(collection.unenriched).to contain_exactly(hash_commit)
+ end
+ end
+
+ describe '#fully_enriched?' do
+ it 'returns true when all commits are backed by gitaly data' do
+ collection = described_class.new(project, [gitaly_commit, gitaly_commit])
+
+ expect(collection.fully_enriched?).to eq(true)
+ end
+
+ it 'returns false when any commits are not backed by gitaly data' do
+ collection = described_class.new(project, [gitaly_commit, hash_commit])
+
+ expect(collection.fully_enriched?).to eq(false)
+ end
+
+ it 'returns true when the collection is empty' do
+ collection = described_class.new(project, [])
+
+ expect(collection.fully_enriched?).to eq(true)
+ end
+ end
+
+ describe '#enrich!' do
+ it 'replaces commits in the collection with those backed by gitaly data' do
+ collection = described_class.new(project, [hash_commit])
+
+ collection.enrich!
+
+ new_commit = collection.commits.first
+ expect(new_commit.id).to eq(hash_commit.id)
+ expect(hash_commit.gitaly_commit?).to eq(false)
+ expect(new_commit.gitaly_commit?).to eq(true)
+ end
+
+ it 'maintains the original order of the commits' do
+ gitaly_commits = [gitaly_commit] * 3
+ hash_commits = [hash_commit] * 3
+ # Interleave the gitaly and hash commits together
+ original_commits = gitaly_commits.zip(hash_commits).flatten
+ collection = described_class.new(project, original_commits)
+
+ collection.enrich!
+
+ original_commits.each_with_index do |original_commit, i|
+ new_commit = collection.commits[i]
+ expect(original_commit.id).to eq(new_commit.id)
+ end
+ end
+
+ it 'fetches data if there are unenriched commits' do
+ collection = described_class.new(project, [hash_commit])
+
+ expect(Commit).to receive(:lazy).exactly(:once)
+
+ collection.enrich!
+ end
+
+ it 'does not fetch data if all commits are enriched' do
+ collection = described_class.new(project, [gitaly_commit])
+
+ expect(Commit).not_to receive(:lazy)
+
+ collection.enrich!
+ end
end
end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index f2efcd9d0e9..b96ca89c893 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CommitRange do
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 9d4e18534ae..14f4b4d692f 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Commit do
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index e2b7f5c6ee2..ca2f9e36c98 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CommitStatus do
diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb
index 0bc3ee014e6..43c3580bed2 100644
--- a/spec/models/compare_spec.rb
+++ b/spec/models/compare_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Compare do
diff --git a/spec/models/concerns/access_requestable_spec.rb b/spec/models/concerns/access_requestable_spec.rb
index 04d6cfa2c02..de2bc3a387b 100644
--- a/spec/models/concerns/access_requestable_spec.rb
+++ b/spec/models/concerns/access_requestable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AccessRequestable do
diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb
index 1ea7f2b9985..c750be6b75c 100644
--- a/spec/models/concerns/avatarable_spec.rb
+++ b/spec/models/concerns/avatarable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Avatarable do
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index 5713106418d..9e7106281ee 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Awardable do
diff --git a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
index e5392fe0462..1fe90d3cc9a 100644
--- a/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
+++ b/spec/models/concerns/batch_destroy_dependent_associations_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BatchDestroyDependentAssociations do
diff --git a/spec/models/concerns/blocks_json_serialization_spec.rb b/spec/models/concerns/blocks_json_serialization_spec.rb
index 5906b588d0e..e8657c45a93 100644
--- a/spec/models/concerns/blocks_json_serialization_spec.rb
+++ b/spec/models/concerns/blocks_json_serialization_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe BlocksJsonSerialization do
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 7d555f15e39..78637ff10c6 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CacheMarkdownField do
diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb
index 43a544cfe26..394fac52aa7 100644
--- a/spec/models/concerns/cacheable_attributes_spec.rb
+++ b/spec/models/concerns/cacheable_attributes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CacheableAttributes do
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index 1bf6c9b3404..d6d41a25eac 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CaseSensitivity do
diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb
index 51221e07ca3..e41d75568f7 100644
--- a/spec/models/concerns/chronic_duration_attribute_spec.rb
+++ b/spec/models/concerns/chronic_duration_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'ChronicDurationAttribute reader' do
diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb
index 6951be903fe..42bed9434f5 100644
--- a/spec/models/concerns/deployable_spec.rb
+++ b/spec/models/concerns/deployable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Deployable do
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 19ab4382b53..0e34d8fccf3 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe DeploymentPlatform do
diff --git a/spec/models/concerns/discussion_on_diff_spec.rb b/spec/models/concerns/discussion_on_diff_spec.rb
index 64bf04071e8..baddca47dfa 100644
--- a/spec/models/concerns/discussion_on_diff_spec.rb
+++ b/spec/models/concerns/discussion_on_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiscussionOnDiff do
diff --git a/spec/models/concerns/each_batch_spec.rb b/spec/models/concerns/each_batch_spec.rb
index 17224c09693..c4cf8e80f7a 100644
--- a/spec/models/concerns/each_batch_spec.rb
+++ b/spec/models/concerns/each_batch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EachBatch do
diff --git a/spec/models/concerns/editable_spec.rb b/spec/models/concerns/editable_spec.rb
index 49a9a8ebcbc..4a4a3ca5687 100644
--- a/spec/models/concerns/editable_spec.rb
+++ b/spec/models/concerns/editable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Editable do
diff --git a/spec/models/concerns/expirable_spec.rb b/spec/models/concerns/expirable_spec.rb
index f7b436f32e6..f4f5eab5b86 100644
--- a/spec/models/concerns/expirable_spec.rb
+++ b/spec/models/concerns/expirable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Expirable do
diff --git a/spec/models/concerns/faster_cache_keys_spec.rb b/spec/models/concerns/faster_cache_keys_spec.rb
index 8d3f94267fa..7830acbae3d 100644
--- a/spec/models/concerns/faster_cache_keys_spec.rb
+++ b/spec/models/concerns/faster_cache_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FasterCacheKeys do
diff --git a/spec/models/concerns/feature_gate_spec.rb b/spec/models/concerns/feature_gate_spec.rb
index 3f601243245..276d3d9e1d5 100644
--- a/spec/models/concerns/feature_gate_spec.rb
+++ b/spec/models/concerns/feature_gate_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FeatureGate do
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index 28352d8c961..194caac3fce 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupDescendant, :nested_groups do
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index e8b1eba67cc..a217dc42537 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HasStatus do
diff --git a/spec/models/concerns/has_variable_spec.rb b/spec/models/concerns/has_variable_spec.rb
index bff96e12ffa..2bb21d7934e 100644
--- a/spec/models/concerns/has_variable_spec.rb
+++ b/spec/models/concerns/has_variable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HasVariable do
diff --git a/spec/models/concerns/ignorable_column_spec.rb b/spec/models/concerns/ignorable_column_spec.rb
index b70f2331a0e..6b82825d2cc 100644
--- a/spec/models/concerns/ignorable_column_spec.rb
+++ b/spec/models/concerns/ignorable_column_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IgnorableColumn do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 72c6161424b..259ac6852a8 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issuable do
diff --git a/spec/models/concerns/loaded_in_group_list_spec.rb b/spec/models/concerns/loaded_in_group_list_spec.rb
index 7a279547a3a..7c97b580779 100644
--- a/spec/models/concerns/loaded_in_group_list_spec.rb
+++ b/spec/models/concerns/loaded_in_group_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LoadedInGroupList do
diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb
index ff4a04ea573..ee32e3b165b 100644
--- a/spec/models/concerns/manual_inverse_association_spec.rb
+++ b/spec/models/concerns/manual_inverse_association_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ManualInverseAssociation do
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index a9b237fa9ea..f31e3e8821d 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mentionable do
diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb
index 81ca5b638fe..22d4b2cc517 100644
--- a/spec/models/concerns/milestoneish_spec.rb
+++ b/spec/models/concerns/milestoneish_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Milestone, 'Milestoneish' do
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index 485a6e165a1..ee613b199ad 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Noteable do
diff --git a/spec/models/concerns/participable_spec.rb b/spec/models/concerns/participable_spec.rb
index 431f1482615..3d5937c4fc6 100644
--- a/spec/models/concerns/participable_spec.rb
+++ b/spec/models/concerns/participable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Participable do
diff --git a/spec/models/concerns/presentable_spec.rb b/spec/models/concerns/presentable_spec.rb
index 941647a79fb..9db868dd348 100644
--- a/spec/models/concerns/presentable_spec.rb
+++ b/spec/models/concerns/presentable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Presentable do
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 9041690023f..5aa43b58217 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectFeaturesCompatibility do
diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb
index f4b9c57e71a..7148261b1e4 100644
--- a/spec/models/concerns/prometheus_adapter_spec.rb
+++ b/spec/models/concerns/prometheus_adapter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
diff --git a/spec/models/concerns/protected_ref_access_spec.rb b/spec/models/concerns/protected_ref_access_spec.rb
index ce602337647..94798f0590d 100644
--- a/spec/models/concerns/protected_ref_access_spec.rb
+++ b/spec/models/concerns/protected_ref_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedRefAccess do
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 03ae45e6b17..32e13d5abed 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ReactiveCaching, :use_clean_rails_memory_store_caching do
diff --git a/spec/models/concerns/redactable_spec.rb b/spec/models/concerns/redactable_spec.rb
index 7feeaa54069..57c7d2cb767 100644
--- a/spec/models/concerns/redactable_spec.rb
+++ b/spec/models/concerns/redactable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Redactable do
diff --git a/spec/models/concerns/redis_cacheable_spec.rb b/spec/models/concerns/redis_cacheable_spec.rb
index 23c6c6233e9..a9dca27f258 100644
--- a/spec/models/concerns/redis_cacheable_spec.rb
+++ b/spec/models/concerns/redis_cacheable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RedisCacheable do
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
index ac8da30b6c9..d0ae45f7871 100644
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ b/spec/models/concerns/relative_positioning_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RelativePositioning do
diff --git a/spec/models/concerns/resolvable_discussion_spec.rb b/spec/models/concerns/resolvable_discussion_spec.rb
index 97b046b0f21..9ea01ca9002 100644
--- a/spec/models/concerns/resolvable_discussion_spec.rb
+++ b/spec/models/concerns/resolvable_discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Discussion, ResolvableDiscussion do
diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb
index fcb5250278e..4f46252a044 100644
--- a/spec/models/concerns/resolvable_note_spec.rb
+++ b/spec/models/concerns/resolvable_note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Note, ResolvableNote do
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 565266321d3..1fb0dd5030c 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Group, 'Routable' do
diff --git a/spec/models/concerns/sha_attribute_spec.rb b/spec/models/concerns/sha_attribute_spec.rb
index 0d3beb6a6e3..a4a81ae126d 100644
--- a/spec/models/concerns/sha_attribute_spec.rb
+++ b/spec/models/concerns/sha_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ShaAttribute do
diff --git a/spec/models/concerns/sortable_spec.rb b/spec/models/concerns/sortable_spec.rb
index 0a9d2021a19..184f7986a6f 100644
--- a/spec/models/concerns/sortable_spec.rb
+++ b/spec/models/concerns/sortable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Sortable do
diff --git a/spec/models/concerns/spammable_spec.rb b/spec/models/concerns/spammable_spec.rb
index e698207166c..650d49e41a1 100644
--- a/spec/models/concerns/spammable_spec.rb
+++ b/spec/models/concerns/spammable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Spammable do
diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb
index 8c945686b66..5c0d1042e06 100644
--- a/spec/models/concerns/strip_attribute_spec.rb
+++ b/spec/models/concerns/strip_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StripAttribute do
diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb
index 45dfb136aea..2f88adf08dd 100644
--- a/spec/models/concerns/subscribable_spec.rb
+++ b/spec/models/concerns/subscribable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Subscribable, 'Subscribable' do
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 40cb4eef60a..51e28974ae0 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'TokenAuthenticatable' do
diff --git a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
index 2a0182b4294..7332da309d5 100644
--- a/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TokenAuthenticatableStrategies::Base do
diff --git a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
index ca38f86c5ab..70f41981b3b 100644
--- a/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
+++ b/spec/models/concerns/token_authenticatable_strategies/encrypted_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TokenAuthenticatableStrategies::Encrypted do
diff --git a/spec/models/concerns/triggerable_hooks_spec.rb b/spec/models/concerns/triggerable_hooks_spec.rb
index 265abd6bd72..f28e5f56411 100644
--- a/spec/models/concerns/triggerable_hooks_spec.rb
+++ b/spec/models/concerns/triggerable_hooks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe TriggerableHooks do
diff --git a/spec/models/concerns/uniquify_spec.rb b/spec/models/concerns/uniquify_spec.rb
index 6cd2de6dcce..9ba35702ba6 100644
--- a/spec/models/concerns/uniquify_spec.rb
+++ b/spec/models/concerns/uniquify_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Uniquify do
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index e46945e301e..013112d1d51 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRepository do
diff --git a/spec/models/conversational_development_index/metric_spec.rb b/spec/models/conversational_development_index/metric_spec.rb
index b3193619503..60b1a860dfd 100644
--- a/spec/models/conversational_development_index/metric_spec.rb
+++ b/spec/models/conversational_development_index/metric_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe ConversationalDevelopmentIndex::Metric do
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 6a6b58fb52b..b22a0340015 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#code' do
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 45f1b4fe8a3..07d60be091a 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#issue' do
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index d366e2b723a..3d22a284264 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#plan' do
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 156eb96cfce..383727cd8f7 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#production' do
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 0aedfb49cb5..1af5f9cc1f4 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#review' do
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 0cbda50c688..8375944f03c 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#staging' do
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index e58b8fdff58..b78258df564 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CycleAnalytics#test' do
diff --git a/spec/models/cycle_analytics_spec.rb b/spec/models/cycle_analytics_spec.rb
index 0fe24870f02..5d8b5b573cf 100644
--- a/spec/models/cycle_analytics_spec.rb
+++ b/spec/models/cycle_analytics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CycleAnalytics do
diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb
index 41440c6d288..ec6cfb6b826 100644
--- a/spec/models/deploy_key_spec.rb
+++ b/spec/models/deploy_key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeployKey, :mailer do
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index fca3090ff4a..c137444763b 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeployKeysProject do
diff --git a/spec/models/deploy_token_spec.rb b/spec/models/deploy_token_spec.rb
index 3435f93c999..05320703e25 100644
--- a/spec/models/deploy_token_spec.rb
+++ b/spec/models/deploy_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeployToken do
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index 5fce9504334..d9170d5fa07 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Deployment do
diff --git a/spec/models/diff_discussion_spec.rb b/spec/models/diff_discussion_spec.rb
index 50b19000799..cfeb4382927 100644
--- a/spec/models/diff_discussion_spec.rb
+++ b/spec/models/diff_discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiffDiscussion do
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index 67e5f4f7e41..fa19cb47a0d 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiffNote do
diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb
index f4efe5a7b3a..b8bdeb781dc 100644
--- a/spec/models/diff_viewer/base_spec.rb
+++ b/spec/models/diff_viewer/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiffViewer::Base do
diff --git a/spec/models/diff_viewer/server_side_spec.rb b/spec/models/diff_viewer/server_side_spec.rb
index 86b14b6ebf3..27de0584b8a 100644
--- a/spec/models/diff_viewer/server_side_spec.rb
+++ b/spec/models/diff_viewer/server_side_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DiffViewer::ServerSide do
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index a46f7ed6507..0d02165787a 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Discussion do
diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb
index 47eb0717c0c..cae88f39660 100644
--- a/spec/models/email_spec.rb
+++ b/spec/models/email_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Email do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index ab1b306e597..448ed35cb1e 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Environment do
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index 2576a9aba06..c503c35305f 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EnvironmentStatus do
diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
index cbde13a2c7a..21e381d9fb7 100644
--- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb
+++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb
@@ -167,7 +167,7 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
end
end
- context 'when sentry client raises exception' do
+ context 'when sentry client raises Sentry::Client::Error' do
let(:sentry_client) { spy(:sentry_client) }
before do
@@ -179,7 +179,31 @@ describe ErrorTracking::ProjectErrorTrackingSetting do
end
it 'returns error' do
- expect(result).to eq(error: 'error message')
+ expect(result).to eq(
+ error: 'error message',
+ error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE
+ )
+ expect(subject).to have_received(:sentry_client)
+ expect(sentry_client).to have_received(:list_issues)
+ end
+ end
+
+ context 'when sentry client raises Sentry::Client::MissingKeysError' do
+ let(:sentry_client) { spy(:sentry_client) }
+
+ before do
+ synchronous_reactive_cache(subject)
+
+ allow(subject).to receive(:sentry_client).and_return(sentry_client)
+ allow(sentry_client).to receive(:list_issues).with(opts)
+ .and_raise(Sentry::Client::MissingKeysError, 'Sentry API response is missing keys. key not found: "id"')
+ end
+
+ it 'returns error' do
+ expect(result).to eq(
+ error: 'Sentry API response is missing keys. key not found: "id"',
+ error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS
+ )
expect(subject).to have_received(:sentry_client)
expect(sentry_client).to have_received(:list_issues)
end
diff --git a/spec/models/event_collection_spec.rb b/spec/models/event_collection_spec.rb
index 6078f429bdc..efe511042c3 100644
--- a/spec/models/event_collection_spec.rb
+++ b/spec/models/event_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EventCollection do
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index ce4f8ee4705..d192fe70506 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Event do
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 83ba22caa03..9d064d458f0 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExternalIssue do
diff --git a/spec/models/fork_network_member_spec.rb b/spec/models/fork_network_member_spec.rb
index 60d04562e6c..eab758248de 100644
--- a/spec/models/fork_network_member_spec.rb
+++ b/spec/models/fork_network_member_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ForkNetworkMember do
diff --git a/spec/models/fork_network_spec.rb b/spec/models/fork_network_spec.rb
index a43baf1820a..5ec0f8d6b02 100644
--- a/spec/models/fork_network_spec.rb
+++ b/spec/models/fork_network_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ForkNetwork do
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index a3e68d2e646..c851810ffb3 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GenericCommitStatus do
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index f93904065c7..9d901d01a52 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GlobalMilestone do
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index 58a1d2e4ea2..479b39cd139 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe GpgKey do
diff --git a/spec/models/gpg_key_subkey_spec.rb b/spec/models/gpg_key_subkey_spec.rb
index 3c86837f47f..51d2f9cb9ac 100644
--- a/spec/models/gpg_key_subkey_spec.rb
+++ b/spec/models/gpg_key_subkey_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe GpgKeySubkey do
diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb
index e90319c39b1..47c343edf0e 100644
--- a/spec/models/gpg_signature_spec.rb
+++ b/spec/models/gpg_signature_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe GpgSignature do
diff --git a/spec/models/group_custom_attribute_spec.rb b/spec/models/group_custom_attribute_spec.rb
index 7ecb2022567..7d60c74b62b 100644
--- a/spec/models/group_custom_attribute_spec.rb
+++ b/spec/models/group_custom_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupCustomAttribute do
diff --git a/spec/models/group_label_spec.rb b/spec/models/group_label_spec.rb
index d0fc1eaa3ec..a3a5c631c3d 100644
--- a/spec/models/group_label_spec.rb
+++ b/spec/models/group_label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupLabel do
diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb
index fcc33cd95fe..01856870fe0 100644
--- a/spec/models/group_milestone_spec.rb
+++ b/spec/models/group_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupMilestone do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 16624ce47d0..2c6abddca17 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Group do
diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb
index fc30f3056e5..93862e98172 100644
--- a/spec/models/guest_spec.rb
+++ b/spec/models/guest_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Guest do
diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb
index df7edda2213..1249c793f7f 100644
--- a/spec/models/hooks/active_hook_filter_spec.rb
+++ b/spec/models/hooks/active_hook_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ActiveHookFilter do
diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 5dd31b1b5de..a945f0d1516 100644
--- a/spec/models/hooks/project_hook_spec.rb
+++ b/spec/models/hooks/project_hook_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectHook do
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index e32eaafc13f..936c2fbad27 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ServiceHook do
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index edd1cb455af..e0d4d2e4858 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe SystemHook do
diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb
index 744a6ccae8b..f812149c9be 100644
--- a/spec/models/hooks/web_hook_log_spec.rb
+++ b/spec/models/hooks/web_hook_log_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe WebHookLog do
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index a308ac6e33a..fe08dc4f5e6 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WebHook do
diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb
index e1a7a59dfd1..74ddc2d6284 100644
--- a/spec/models/identity_spec.rb
+++ b/spec/models/identity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Identity do
diff --git a/spec/models/import_export_upload_spec.rb b/spec/models/import_export_upload_spec.rb
index 58af84b8a08..18a714f4d98 100644
--- a/spec/models/import_export_upload_spec.rb
+++ b/spec/models/import_export_upload_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ImportExportUpload do
diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb
index cb3d6c7cda2..e65f97df3c3 100644
--- a/spec/models/instance_configuration_spec.rb
+++ b/spec/models/instance_configuration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe InstanceConfiguration do
@@ -30,8 +32,8 @@ describe InstanceConfiguration do
end
def stub_pub_file(exist: true)
- path = 'spec/fixtures/ssh_host_example_key.pub'
- path << 'random' unless exist
+ path = exist ? 'spec/fixtures/ssh_host_example_key.pub' : 'spec/fixtures/ssh_host_example_key.pub.random'
+
allow(subject).to receive(:ssh_algorithm_file).and_return(Rails.root.join(path))
end
end
diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb
index d32f163f05b..ff2382838ae 100644
--- a/spec/models/internal_id_spec.rb
+++ b/spec/models/internal_id_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe InternalId do
diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb
index b7291eebe64..07858fe8a70 100644
--- a/spec/models/issue/metrics_spec.rb
+++ b/spec/models/issue/metrics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issue::Metrics do
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
index 580a98193af..7fc635f100f 100644
--- a/spec/models/issue_collection_spec.rb
+++ b/spec/models/issue_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssueCollection do
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 6101df2e099..892dd053e39 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Issue do
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 06d26ef89f1..a0b6eff88d5 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Key, :mailer do
diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb
index e2b49bc2de7..b160e72e759 100644
--- a/spec/models/label_link_spec.rb
+++ b/spec/models/label_link_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LabelLink do
diff --git a/spec/models/label_priority_spec.rb b/spec/models/label_priority_spec.rb
index 9dcb0f06b20..1a93468290f 100644
--- a/spec/models/label_priority_spec.rb
+++ b/spec/models/label_priority_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LabelPriority do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 3fc6c06b7fa..5174c590a10 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Label do
diff --git a/spec/models/legacy_diff_discussion_spec.rb b/spec/models/legacy_diff_discussion_spec.rb
index dae97b69c84..49ea319fbd1 100644
--- a/spec/models/legacy_diff_discussion_spec.rb
+++ b/spec/models/legacy_diff_discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LegacyDiffDiscussion do
diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb
index 88838b127d2..effd8b08124 100644
--- a/spec/models/lfs_download_object_spec.rb
+++ b/spec/models/lfs_download_object_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe LfsDownloadObject do
diff --git a/spec/models/lfs_file_lock_spec.rb b/spec/models/lfs_file_lock_spec.rb
index 41ca1578b94..aa64d66944b 100644
--- a/spec/models/lfs_file_lock_spec.rb
+++ b/spec/models/lfs_file_lock_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe LfsFileLock do
diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb
index 3f929710862..3d4d4b7d795 100644
--- a/spec/models/lfs_object_spec.rb
+++ b/spec/models/lfs_object_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LfsObject do
diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb
index 0a3180f43e8..3e86ee38566 100644
--- a/spec/models/lfs_objects_project_spec.rb
+++ b/spec/models/lfs_objects_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LfsObjectsProject do
diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb
index dd912eefac1..7037277e580 100644
--- a/spec/models/license_template_spec.rb
+++ b/spec/models/license_template_spec.rb
@@ -1,16 +1,19 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe LicenseTemplate do
describe '#content' do
it 'calls a proc exactly once if provided' do
- lazy = build_template(-> { 'bar' })
- content = lazy.content
+ content_proc = -> { 'bar' }
+ expect(content_proc).to receive(:call).once.and_call_original
+
+ lazy = build_template(content_proc)
- expect(content).to eq('bar')
- expect(content.object_id).to eq(lazy.content.object_id)
+ expect(lazy.content).to eq('bar')
- content.replace('foo')
- expect(lazy.content).to eq('foo')
+ # Subsequent calls should not call proc again
+ expect(lazy.content).to eq('bar')
end
it 'returns a string if provided' do
diff --git a/spec/models/list_spec.rb b/spec/models/list_spec.rb
index a51580f8292..18d4549977c 100644
--- a/spec/models/list_spec.rb
+++ b/spec/models/list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe List do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 188beac1582..c68c3ce2abe 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Member do
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index a3451c67bd8..f227abd3dae 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -1,6 +1,24 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupMember do
+ describe '.count_users_by_group_id' do
+ it 'counts users by group ID' do
+ user_1 = create(:user)
+ user_2 = create(:user)
+ group_1 = create(:group)
+ group_2 = create(:group)
+
+ group_1.add_owner(user_1)
+ group_1.add_owner(user_2)
+ group_2.add_owner(user_1)
+
+ expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
+ group_2.id => 1)
+ end
+ end
+
describe '.access_level_roles' do
it 'returns Gitlab::Access.options_with_owner' do
expect(described_class.access_level_roles).to eq(Gitlab::Access.options_with_owner)
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 36bfff2c339..497764b6825 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectMember do
diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
index 02ff7839739..49573af0fed 100644
--- a/spec/models/merge_request/metrics_spec.rb
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequest::Metrics do
diff --git a/spec/models/merge_request_diff_commit_spec.rb b/spec/models/merge_request_diff_commit_spec.rb
index 10487190a44..ab2aadf7d88 100644
--- a/spec/models/merge_request_diff_commit_spec.rb
+++ b/spec/models/merge_request_diff_commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe MergeRequestDiffCommit do
diff --git a/spec/models/merge_request_diff_file_spec.rb b/spec/models/merge_request_diff_file_spec.rb
index faa47660a74..66957c24fdc 100644
--- a/spec/models/merge_request_diff_file_spec.rb
+++ b/spec/models/merge_request_diff_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe MergeRequestDiffFile do
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index 53f5307ea0b..a53add67066 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequestDiff do
@@ -51,7 +53,104 @@ describe MergeRequestDiff do
end
end
- describe '#latest' do
+ describe '.ids_for_external_storage_migration' do
+ set(:merge_request) { create(:merge_request) }
+ set(:outdated) { merge_request.merge_request_diff }
+ set(:latest) { merge_request.create_merge_request_diff }
+
+ set(:closed_mr) { create(:merge_request, :closed_last_month) }
+ let(:closed) { closed_mr.merge_request_diff }
+
+ set(:merged_mr) { create(:merge_request, :merged_last_month) }
+ let(:merged) { merged_mr.merge_request_diff }
+
+ set(:recently_closed_mr) { create(:merge_request, :closed) }
+ let(:closed_recently) { recently_closed_mr.merge_request_diff }
+
+ set(:recently_merged_mr) { create(:merge_request, :merged) }
+ let(:merged_recently) { recently_merged_mr.merge_request_diff }
+
+ before do
+ merge_request.update!(latest_merge_request_diff: latest)
+ end
+
+ subject { described_class.ids_for_external_storage_migration(limit: 1000) }
+
+ context 'external diffs are disabled' do
+ before do
+ stub_external_diffs_setting(enabled: false)
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'external diffs are misconfigured' do
+ before do
+ stub_external_diffs_setting(enabled: true, when: 'every second tuesday')
+ end
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'external diffs are enabled unconditionally' do
+ before do
+ stub_external_diffs_setting(enabled: true)
+ end
+
+ it { is_expected.to contain_exactly(outdated.id, latest.id, closed.id, merged.id, closed_recently.id, merged_recently.id) }
+ end
+
+ context 'external diffs are enabled for outdated diffs' do
+ before do
+ stub_external_diffs_setting(enabled: true, when: 'outdated')
+ end
+
+ it 'returns records for outdated merge request versions' do
+ is_expected.to contain_exactly(outdated.id, closed.id, merged.id)
+ end
+ end
+
+ context 'with limit' do
+ it 'respects the limit' do
+ stub_external_diffs_setting(enabled: true)
+
+ expect(described_class.ids_for_external_storage_migration(limit: 3).count).to eq(3)
+ end
+ end
+ end
+
+ describe '#migrate_files_to_external_storage!' do
+ let(:diff) { create(:merge_request).merge_request_diff }
+
+ it 'converts from in-database to external storage' do
+ expect(diff).not_to be_stored_externally
+
+ stub_external_diffs_setting(enabled: true)
+ expect(diff).to receive(:save!)
+
+ diff.migrate_files_to_external_storage!
+
+ expect(diff).to be_stored_externally
+ end
+
+ it 'does nothing with an external diff' do
+ stub_external_diffs_setting(enabled: true)
+
+ expect(diff).to be_stored_externally
+ expect(diff).not_to receive(:save!)
+
+ diff.migrate_files_to_external_storage!
+ end
+
+ it 'does nothing if external diffs are disabled' do
+ expect(diff).not_to be_stored_externally
+ expect(diff).not_to receive(:save!)
+
+ diff.migrate_files_to_external_storage!
+ end
+ end
+
+ describe '#latest?' do
let!(:mr) { create(:merge_request, :with_diffs) }
let!(:first_diff) { mr.merge_request_diff }
let!(:last_diff) { mr.create_merge_request_diff }
@@ -222,12 +321,56 @@ describe MergeRequestDiff do
include_examples 'merge request diffs'
end
- describe 'external diffs configured' do
+ describe 'external diffs always enabled' do
before do
- stub_external_diffs_setting(enabled: true)
+ stub_external_diffs_setting(enabled: true, when: 'always')
+ end
+
+ include_examples 'merge request diffs'
+ end
+
+ describe 'exernal diffs enabled for outdated diffs' do
+ before do
+ stub_external_diffs_setting(enabled: true, when: 'outdated')
end
include_examples 'merge request diffs'
+
+ it 'stores up-to-date diffs in the database' do
+ expect(diff).not_to be_stored_externally
+ end
+
+ it 'stores diffs for recently closed MRs in the database' do
+ mr = create(:merge_request, :closed)
+
+ expect(mr.merge_request_diff).not_to be_stored_externally
+ end
+
+ it 'stores diffs for recently merged MRs in the database' do
+ mr = create(:merge_request, :merged)
+
+ expect(mr.merge_request_diff).not_to be_stored_externally
+ end
+
+ it 'stores diffs for old MR versions in external storage' do
+ old_diff = diff
+ merge_request.create_merge_request_diff
+ old_diff.migrate_files_to_external_storage!
+
+ expect(old_diff).to be_stored_externally
+ end
+
+ it 'stores diffs for old closed MRs in external storage' do
+ mr = create(:merge_request, :closed_last_month)
+
+ expect(mr.merge_request_diff).to be_stored_externally
+ end
+
+ it 'stores diffs for old merged MRs in external storage' do
+ mr = create(:merge_request, :merged_last_month)
+
+ expect(mr.merge_request_diff).to be_stored_externally
+ end
end
describe '#commit_shas' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 22998bc5b6a..fb32f72e2de 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeRequest do
@@ -84,32 +86,27 @@ describe MergeRequest do
describe '#default_squash_commit_message' do
let(:project) { subject.project }
-
- def commit_collection(commit_hashes)
- raw_commits = commit_hashes.map { |raw| Commit.from_hash(raw, project) }
-
- CommitCollection.new(project, raw_commits)
- end
+ let(:is_multiline) { -> (c) { c.description.present? } }
+ let(:multiline_commits) { subject.commits.select(&is_multiline) }
+ let(:singleline_commits) { subject.commits.reject(&is_multiline) }
it 'returns the oldest multiline commit message' do
- commits = commit_collection([
- { message: 'Singleline', parent_ids: [] },
- { message: "Second multiline\nCommit message", parent_ids: [] },
- { message: "First multiline\nCommit message", parent_ids: [] }
- ])
-
- expect(subject).to receive(:commits).and_return(commits)
-
- expect(subject.default_squash_commit_message).to eq("First multiline\nCommit message")
+ expect(subject.default_squash_commit_message).to eq(multiline_commits.last.message)
end
it 'returns the merge request title if there are no multiline commits' do
- commits = commit_collection([
- { message: 'Singleline', parent_ids: [] }
- ])
+ expect(subject).to receive(:commits).and_return(
+ CommitCollection.new(project, singleline_commits)
+ )
+
+ expect(subject.default_squash_commit_message).to eq(subject.title)
+ end
- expect(subject).to receive(:commits).and_return(commits)
+ it 'does not return commit messages from multiline merge commits' do
+ collection = CommitCollection.new(project, multiline_commits).enrich!
+ expect(collection.commits).to all( receive(:merge_commit?).and_return(true) )
+ expect(subject).to receive(:commits).and_return(collection)
expect(subject.default_squash_commit_message).to eq(subject.title)
end
end
@@ -184,6 +181,31 @@ describe MergeRequest do
expect(MergeRequest::Metrics.count).to eq(1)
end
end
+
+ describe '#refresh_merge_request_assignees' do
+ set(:user) { create(:user) }
+
+ it 'creates merge request assignees relation upon MR creation' do
+ merge_request = create(:merge_request, assignee: nil)
+
+ expect(merge_request.merge_request_assignees).to be_empty
+
+ expect { merge_request.update!(assignee: user) }
+ .to change { merge_request.reload.merge_request_assignees.count }
+ .from(0).to(1)
+ end
+
+ it 'updates merge request assignees relation upon MR assignee change' do
+ another_user = create(:user)
+ merge_request = create(:merge_request, assignee: user)
+
+ expect { merge_request.update!(assignee: another_user) }
+ .to change { merge_request.reload.merge_request_assignees.first.assignee }
+ .from(user).to(another_user)
+
+ expect(merge_request.merge_request_assignees.count).to eq(1)
+ end
+ end
end
describe 'respond to' do
@@ -1045,7 +1067,7 @@ describe MergeRequest do
describe '#commit_authors' do
it 'returns all the authors of every commit in the merge request' do
- users = subject.commits.map(&:author_email).uniq.map do |email|
+ users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email|
create(:user, email: email)
end
@@ -1059,7 +1081,7 @@ describe MergeRequest do
describe '#authors' do
it 'returns a list with all the commit authors in the merge request and author' do
- users = subject.commits.map(&:author_email).uniq.map do |email|
+ users = subject.commits.without_merge_commits.map(&:author_email).uniq.map do |email|
create(:user, email: email)
end
@@ -2692,14 +2714,21 @@ describe MergeRequest do
end
describe '#has_commits?' do
- before do
+ it 'returns true when merge request diff has commits' do
allow(subject.merge_request_diff).to receive(:commits_count)
.and_return(2)
- end
- it 'returns true when merge request diff has commits' do
expect(subject.has_commits?).to be_truthy
end
+
+ context 'when commits_count is nil' do
+ it 'returns false' do
+ allow(subject.merge_request_diff).to receive(:commits_count)
+ .and_return(nil)
+
+ expect(subject.has_commits?).to be_falsey
+ end
+ end
end
describe '#has_no_commits?' do
@@ -3061,6 +3090,38 @@ describe MergeRequest do
end
end
+ describe '#mergeable_to_ref?' do
+ it 'returns true when merge request is mergeable' do
+ subject = create(:merge_request)
+
+ expect(subject.mergeable_to_ref?).to be(true)
+ end
+
+ it 'returns false when merge request is already merged' do
+ subject = create(:merge_request, :merged)
+
+ expect(subject.mergeable_to_ref?).to be(false)
+ end
+
+ it 'returns false when merge request is closed' do
+ subject = create(:merge_request, :closed)
+
+ expect(subject.mergeable_to_ref?).to be(false)
+ end
+
+ it 'returns false when merge request is work in progress' do
+ subject = create(:merge_request, title: 'WIP: The feature')
+
+ expect(subject.mergeable_to_ref?).to be(false)
+ end
+
+ it 'returns false when merge request has no commits' do
+ subject = create(:merge_request, source_branch: 'empty-branch', target_branch: 'master')
+
+ expect(subject.mergeable_to_ref?).to be(false)
+ end
+ end
+
describe '#merge_participants' do
it 'contains author' do
expect(subject.merge_participants).to eq([subject.author])
@@ -3095,4 +3156,32 @@ describe MergeRequest do
end
end
end
+
+ describe '.merge_request_ref?' do
+ subject { described_class.merge_request_ref?(ref) }
+
+ context 'when ref is ref name of a branch' do
+ let(:ref) { 'feature' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when ref is HEAD ref path of a branch' do
+ let(:ref) { 'refs/heads/feature' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when ref is HEAD ref path of a merge request' do
+ let(:ref) { 'refs/merge-requests/1/head' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when ref is merge ref path of a merge request' do
+ let(:ref) { 'refs/merge-requests/1/merge' }
+
+ it { is_expected.to be_truthy }
+ end
+ end
end
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 717d4ae4ec0..b82368318f2 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Milestone do
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index aadc298ae0b..62e7dd3231b 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Namespace do
diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb
index c364dd6643b..d1a2bedf542 100644
--- a/spec/models/network/graph_spec.rb
+++ b/spec/models/network/graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Network::Graph do
diff --git a/spec/models/note_diff_file_spec.rb b/spec/models/note_diff_file_spec.rb
index 591c1a89748..99eeac8d778 100644
--- a/spec/models/note_diff_file_spec.rb
+++ b/spec/models/note_diff_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe NoteDiffFile do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index eb6f6ff5faf..7a1ab20186a 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Note do
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index 13fe47799ed..3710f2be287 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NotificationRecipient do
diff --git a/spec/models/notification_setting_spec.rb b/spec/models/notification_setting_spec.rb
index c8ab564e3bc..85128456918 100644
--- a/spec/models/notification_setting_spec.rb
+++ b/spec/models/notification_setting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe NotificationSetting do
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 4b85c5e8720..142ddebbbf8 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PagesDomain do
diff --git a/spec/models/personal_access_token_spec.rb b/spec/models/personal_access_token_spec.rb
index c82ab9c9e62..e0e1101ffc6 100644
--- a/spec/models/personal_access_token_spec.rb
+++ b/spec/models/personal_access_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PersonalAccessToken do
diff --git a/spec/models/programming_language_spec.rb b/spec/models/programming_language_spec.rb
index 99cd358f863..b327d360461 100644
--- a/spec/models/programming_language_spec.rb
+++ b/spec/models/programming_language_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProgrammingLanguage do
diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb
index c289ee0859a..6f06fe4e55a 100644
--- a/spec/models/project_authorization_spec.rb
+++ b/spec/models/project_authorization_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectAuthorization do
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 7ff64c76e37..8ad28ce68cc 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectAutoDevops do
diff --git a/spec/models/project_custom_attribute_spec.rb b/spec/models/project_custom_attribute_spec.rb
index 669de5506bc..80638676b49 100644
--- a/spec/models/project_custom_attribute_spec.rb
+++ b/spec/models/project_custom_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectCustomAttribute do
diff --git a/spec/models/project_deploy_token_spec.rb b/spec/models/project_deploy_token_spec.rb
index 9e2e40c2e8f..2a5fefc1ab0 100644
--- a/spec/models/project_deploy_token_spec.rb
+++ b/spec/models/project_deploy_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe ProjectDeployToken, type: :model do
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index fee7d65c217..50c9d5968ac 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectFeature do
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 5bea21427d4..dad5506900b 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectGroupLink do
diff --git a/spec/models/project_import_state_spec.rb b/spec/models/project_import_state_spec.rb
index e3b2d971419..472bf8f9713 100644
--- a/spec/models/project_import_state_spec.rb
+++ b/spec/models/project_import_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe ProjectImportState, type: :model do
diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb
index 689d4e505e5..330aab9f856 100644
--- a/spec/models/project_label_spec.rb
+++ b/spec/models/project_label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectLabel do
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index e66109fd98f..18e839bcc64 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AsanaService do
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 5cb6d63659e..7742e33e901 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AssemblaService do
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index b880d90d28f..08c510f09df 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BambooService, :use_clean_rails_memory_store_caching do
diff --git a/spec/models/project_services/bugzilla_service_spec.rb b/spec/models/project_services/bugzilla_service_spec.rb
index 43f7bcb1a19..6818db48fee 100644
--- a/spec/models/project_services/bugzilla_service_spec.rb
+++ b/spec/models/project_services/bugzilla_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BugzillaService do
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 1615a93a4ca..091d4d8f695 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildkiteService, :use_clean_rails_memory_store_caching do
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
index ed8347edffd..bf4c52fc7ab 100644
--- a/spec/models/project_services/campfire_service_spec.rb
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CampfireService do
diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb
index f7a35fdc88a..d3adc62c38e 100644
--- a/spec/models/project_services/chat_message/issue_message_spec.rb
+++ b/spec/models/project_services/chat_message/issue_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::IssueMessage do
diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb
index 7997b5bb6b9..b56eb19dd55 100644
--- a/spec/models/project_services/chat_message/merge_message_spec.rb
+++ b/spec/models/project_services/chat_message/merge_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::MergeMessage do
diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb
index 5abbd7bec18..5e7987dc0f6 100644
--- a/spec/models/project_services/chat_message/note_message_spec.rb
+++ b/spec/models/project_services/chat_message/note_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::NoteMessage do
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index 0ff20400999..8f9fa310ad4 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::PipelineMessage do
diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb
index 973d6bdb2a0..a89645a3ea8 100644
--- a/spec/models/project_services/chat_message/push_message_spec.rb
+++ b/spec/models/project_services/chat_message/push_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::PushMessage do
diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
index 7efcba9bcfd..c3db516f253 100644
--- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb
+++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatMessage::WikiPageMessage do
diff --git a/spec/models/project_services/chat_notification_service_spec.rb b/spec/models/project_services/chat_notification_service_spec.rb
index 46713df77da..6f4ddd223f6 100644
--- a/spec/models/project_services/chat_notification_service_spec.rb
+++ b/spec/models/project_services/chat_notification_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ChatNotificationService do
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 7e1b1a4f2af..f0e7551693d 100644
--- a/spec/models/project_services/custom_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CustomIssueTrackerService do
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index 26597d9b83c..22df19d943f 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DroneCiService, :use_clean_rails_memory_store_caching do
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
index d9b7010e5e5..0a58eb367e3 100644
--- a/spec/models/project_services/emails_on_push_service_spec.rb
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EmailsOnPushService do
diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
index 62fd97b038b..bdd8605436f 100644
--- a/spec/models/project_services/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExternalWikiService do
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index fabcb142858..c1ebe69ee66 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FlowdockService do
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 3237b660a16..11f96c03d46 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabIssueTrackerService do
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index cb9ca76fc3f..2e1f6964692 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'socket'
require 'json'
diff --git a/spec/models/project_services/issue_tracker_service_spec.rb b/spec/models/project_services/issue_tracker_service_spec.rb
index e6a1752576b..2fc4d69c2db 100644
--- a/spec/models/project_services/issue_tracker_service_spec.rb
+++ b/spec/models/project_services/issue_tracker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssueTrackerService do
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 5428fcb1271..4a7eee1fbf3 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe JiraService do
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 56e587262ef..7bf093b71e7 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe KubernetesService, :use_clean_rails_memory_store_caching do
diff --git a/spec/models/project_services/mattermost_service_spec.rb b/spec/models/project_services/mattermost_service_spec.rb
index 10c62ca55a7..6261c70f266 100644
--- a/spec/models/project_services/mattermost_service_spec.rb
+++ b/spec/models/project_services/mattermost_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MattermostService do
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 1983e0cc967..87e482059f2 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MattermostSlashCommandsService do
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 3351c6280b4..521d5265753 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MicrosoftTeamsService do
diff --git a/spec/models/project_services/packagist_service_spec.rb b/spec/models/project_services/packagist_service_spec.rb
index 6acee311700..53f18a1bdd9 100644
--- a/spec/models/project_services/packagist_service_spec.rb
+++ b/spec/models/project_services/packagist_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PackagistService do
diff --git a/spec/models/project_services/pipelines_email_service_spec.rb b/spec/models/project_services/pipelines_email_service_spec.rb
index 75ae2207910..ca17e7453b8 100644
--- a/spec/models/project_services/pipelines_email_service_spec.rb
+++ b/spec/models/project_services/pipelines_email_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelinesEmailService, :mailer do
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
index f7d2372eca2..de2c8790405 100644
--- a/spec/models/project_services/pivotaltracker_service_spec.rb
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PivotaltrackerService do
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 54b8c658ff6..d2a45f48705 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PushoverService do
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
index 2ac14eab5e1..ac570ac27e1 100644
--- a/spec/models/project_services/redmine_service_spec.rb
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RedmineService do
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 13cf4d1915e..01f580c5d01 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SlackService do
diff --git a/spec/models/project_services/slack_slash_commands_service_spec.rb b/spec/models/project_services/slack_slash_commands_service_spec.rb
index 5c4bce90ace..8c57907d064 100644
--- a/spec/models/project_services/slack_slash_commands_service_spec.rb
+++ b/spec/models/project_services/slack_slash_commands_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SlackSlashCommandsService do
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index 64b4efca43a..96dccae733b 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TeamcityService, :use_clean_rails_memory_store_caching do
diff --git a/spec/models/project_services/youtrack_service_spec.rb b/spec/models/project_services/youtrack_service_spec.rb
index 9524b526a46..bf9d892f66c 100644
--- a/spec/models/project_services/youtrack_service_spec.rb
+++ b/spec/models/project_services/youtrack_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe YoutrackService do
diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb
index 1b439bcfad1..e87b4f41f4d 100644
--- a/spec/models/project_snippet_spec.rb
+++ b/spec/models/project_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectSnippet do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 1ea54eeb4f7..2158d3cf3e6 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Project do
@@ -2710,7 +2712,7 @@ describe Project do
end
describe '#any_lfs_file_locks?', :request_store do
- set(:project) { create(:project) }
+ let!(:project) { create(:project) }
it 'returns false when there are no LFS file locks' do
expect(project.any_lfs_file_locks?).to be_falsey
@@ -3148,6 +3150,53 @@ describe Project do
expect(projects).to eq([public_project])
end
end
+
+ context 'with requested visibility levels' do
+ set(:internal_project) { create(:project, :internal, :repository) }
+ set(:private_project_2) { create(:project, :private) }
+
+ context 'with admin user' do
+ set(:admin) { create(:admin) }
+
+ it 'returns all projects' do
+ projects = described_class.all.public_or_visible_to_user(admin, [])
+
+ expect(projects).to match_array([public_project, private_project, private_project_2, internal_project])
+ end
+
+ it 'returns all public and private projects' do
+ projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE])
+
+ expect(projects).to match_array([public_project, private_project, private_project_2])
+ end
+
+ it 'returns all private projects' do
+ projects = described_class.all.public_or_visible_to_user(admin, [Gitlab::VisibilityLevel::PRIVATE])
+
+ expect(projects).to match_array([private_project, private_project_2])
+ end
+ end
+
+ context 'with regular user' do
+ it 'returns authorized projects' do
+ projects = described_class.all.public_or_visible_to_user(user, [])
+
+ expect(projects).to match_array([public_project, private_project, internal_project])
+ end
+
+ it "returns user's public and private projects" do
+ projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::PRIVATE])
+
+ expect(projects).to match_array([public_project, private_project])
+ end
+
+ it 'returns one private project' do
+ projects = described_class.all.public_or_visible_to_user(user, [Gitlab::VisibilityLevel::PRIVATE])
+
+ expect(projects).to eq([private_project])
+ end
+ end
+ end
end
describe '.with_feature_available_for_user' do
@@ -3428,7 +3477,7 @@ describe Project do
end
it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the project repo is in use' do
- Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: false)).increase
+ Gitlab::ReferenceCounter.new(Gitlab::GlRepository::PROJECT.identifier_for_subject(project)).increase
expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
@@ -3436,7 +3485,7 @@ describe Project do
end
it 'schedules HashedStorage::ProjectMigrateWorker with delayed start when the wiki repo is in use' do
- Gitlab::ReferenceCounter.new(project.gl_repository(is_wiki: true)).increase
+ Gitlab::ReferenceCounter.new(Gitlab::GlRepository::WIKI.identifier_for_subject(project)).increase
expect(HashedStorage::ProjectMigrateWorker).to receive(:perform_in)
@@ -3569,16 +3618,6 @@ describe Project do
end
end
- describe '#gl_repository' do
- let(:project) { create(:project) }
-
- it 'delegates to Gitlab::GlRepository.gl_repository' do
- expect(Gitlab::GlRepository).to receive(:gl_repository).with(project, true)
-
- project.gl_repository(is_wiki: true)
- end
- end
-
describe '#has_ci?' do
set(:project) { create(:project) }
let(:repository) { double }
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index 64c39f09e33..c670b6aac56 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe ProjectStatistics do
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 3537dead5d1..a2d4fad9292 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe ProjectTeam do
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 7be8d67ba9e..7ea67f31534 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require "spec_helper"
describe ProjectWiki do
diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb
index d4433a88a15..aca3df9fdde 100644
--- a/spec/models/protectable_dropdown_spec.rb
+++ b/spec/models/protectable_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectableDropdown do
diff --git a/spec/models/protected_branch/merge_access_level_spec.rb b/spec/models/protected_branch/merge_access_level_spec.rb
index 612e4a0e332..39dd586b157 100644
--- a/spec/models/protected_branch/merge_access_level_spec.rb
+++ b/spec/models/protected_branch/merge_access_level_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranch::MergeAccessLevel do
diff --git a/spec/models/protected_branch/push_access_level_spec.rb b/spec/models/protected_branch/push_access_level_spec.rb
index 9ccdc22fd41..628c8d29ecd 100644
--- a/spec/models/protected_branch/push_access_level_spec.rb
+++ b/spec/models/protected_branch/push_access_level_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranch::PushAccessLevel do
diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb
index dafe7646366..267434a4148 100644
--- a/spec/models/protected_branch_spec.rb
+++ b/spec/models/protected_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedBranch do
diff --git a/spec/models/protected_tag_spec.rb b/spec/models/protected_tag_spec.rb
index e5a0f6ec23f..79120d17d39 100644
--- a/spec/models/protected_tag_spec.rb
+++ b/spec/models/protected_tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProtectedTag do
diff --git a/spec/models/push_event_payload_spec.rb b/spec/models/push_event_payload_spec.rb
index 69a4922b6fd..6b59ee5ee57 100644
--- a/spec/models/push_event_payload_spec.rb
+++ b/spec/models/push_event_payload_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PushEventPayload do
diff --git a/spec/models/push_event_spec.rb b/spec/models/push_event_spec.rb
index bfe7a30b96a..f86500f91cd 100644
--- a/spec/models/push_event_spec.rb
+++ b/spec/models/push_event_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PushEvent do
diff --git a/spec/models/redirect_route_spec.rb b/spec/models/redirect_route_spec.rb
index 106ae59af29..6ecb5c31c7e 100644
--- a/spec/models/redirect_route_spec.rb
+++ b/spec/models/redirect_route_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe RedirectRoute do
diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb
index 157c96c1f65..b4b32c95dee 100644
--- a/spec/models/release_spec.rb
+++ b/spec/models/release_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe Release do
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index c06e9a08ab4..0478094034a 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe RemoteMirror, :mailer do
diff --git a/spec/models/repository_language_spec.rb b/spec/models/repository_language_spec.rb
index e2e4beb512f..13a4cd1e7cf 100644
--- a/spec/models/repository_language_spec.rb
+++ b/spec/models/repository_language_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryLanguage do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 6599b4e765a..2578208659a 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Repository do
diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb
index 48799781b87..20289afbeb5 100644
--- a/spec/models/route_spec.rb
+++ b/spec/models/route_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Route do
diff --git a/spec/models/sent_notification_spec.rb b/spec/models/sent_notification_spec.rb
index 6c35ed8f649..09be90b82ed 100644
--- a/spec/models/sent_notification_spec.rb
+++ b/spec/models/sent_notification_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SentNotification do
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 25eecb3f909..2f025038bab 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Service do
diff --git a/spec/models/snippet_blob_spec.rb b/spec/models/snippet_blob_spec.rb
index 7c71c458fcc..88441e39d45 100644
--- a/spec/models/snippet_blob_spec.rb
+++ b/spec/models/snippet_blob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SnippetBlob do
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 664dc3fa145..3524cdae3b8 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Snippet do
diff --git a/spec/models/spam_log_spec.rb b/spec/models/spam_log_spec.rb
index 90a2caaeb88..e9ea234f75d 100644
--- a/spec/models/spam_log_spec.rb
+++ b/spec/models/spam_log_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SpamLog do
diff --git a/spec/models/ssh_host_key_spec.rb b/spec/models/ssh_host_key_spec.rb
index 4c677569561..a17cd8ba345 100644
--- a/spec/models/ssh_host_key_spec.rb
+++ b/spec/models/ssh_host_key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SshHostKey do
diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb
index 9e4c2620d82..41bd48810b2 100644
--- a/spec/models/subscription_spec.rb
+++ b/spec/models/subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Subscription do
diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb
index 1e3f587e460..bcd3c03f947 100644
--- a/spec/models/system_note_metadata_spec.rb
+++ b/spec/models/system_note_metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemNoteMetadata do
diff --git a/spec/models/term_agreement_spec.rb b/spec/models/term_agreement_spec.rb
index 950dfa09a6a..42a48048b67 100644
--- a/spec/models/term_agreement_spec.rb
+++ b/spec/models/term_agreement_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TermAgreement do
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
index a0c93c531ea..9d69a0ab148 100644
--- a/spec/models/timelog_spec.rb
+++ b/spec/models/timelog_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
RSpec.describe Timelog do
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index 3682e21ca40..b5bf294790a 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Todo do
diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb
index 6bdb62a0864..c2d5dfdf9c4 100644
--- a/spec/models/tree_spec.rb
+++ b/spec/models/tree_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Tree do
diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb
index 3b5e7ca0d39..619fc8e7d38 100644
--- a/spec/models/trending_project_spec.rb
+++ b/spec/models/trending_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TrendingProject do
diff --git a/spec/models/upload_spec.rb b/spec/models/upload_spec.rb
index 5a0df9fbbb0..02702cb2497 100644
--- a/spec/models/upload_spec.rb
+++ b/spec/models/upload_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Upload do
diff --git a/spec/models/user_agent_detail_spec.rb b/spec/models/user_agent_detail_spec.rb
index b4669f8c1c2..f191d245045 100644
--- a/spec/models/user_agent_detail_spec.rb
+++ b/spec/models/user_agent_detail_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe UserAgentDetail do
diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb
index d54355afe12..b87f6f03d6f 100644
--- a/spec/models/user_callout_spec.rb
+++ b/spec/models/user_callout_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe UserCallout do
diff --git a/spec/models/user_custom_attribute_spec.rb b/spec/models/user_custom_attribute_spec.rb
index 37fc3cb64f0..d0981b2d771 100644
--- a/spec/models/user_custom_attribute_spec.rb
+++ b/spec/models/user_custom_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UserCustomAttribute do
diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb
index cb4bb3372d4..47d919c1d12 100644
--- a/spec/models/user_interacted_project_spec.rb
+++ b/spec/models/user_interacted_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UserInteractedProject do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1be29d039a7..b7e36748fa2 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe User do
diff --git a/spec/models/wiki_directory_spec.rb b/spec/models/wiki_directory_spec.rb
index fb8575cfe2b..5fbcccf897e 100644
--- a/spec/models/wiki_directory_spec.rb
+++ b/spec/models/wiki_directory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe WikiDirectory do
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index cba22b2cc4e..e68da67818a 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe WikiPage do
diff --git a/spec/policies/board_policy_spec.rb b/spec/policies/board_policy_spec.rb
index 4b76d65ef69..52c23951e37 100644
--- a/spec/policies/board_policy_spec.rb
+++ b/spec/policies/board_policy_spec.rb
@@ -17,14 +17,6 @@ describe BoardPolicy do
]
end
- def expect_allowed(*permissions)
- permissions.each { |p| is_expected.to be_allowed(p) }
- end
-
- def expect_disallowed(*permissions)
- permissions.each { |p| is_expected.not_to be_allowed(p) }
- end
-
context 'group board' do
subject { described_class.new(user, group_board) }
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 92bdaa8b8b8..dc98baca6dc 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -1,63 +1,7 @@
require 'spec_helper'
describe GroupPolicy do
- let(:guest) { create(:user) }
- let(:reporter) { create(:user) }
- let(:developer) { create(:user) }
- let(:maintainer) { create(:user) }
- let(:owner) { create(:user) }
- let(:admin) { create(:admin) }
- let(:group) { create(:group, :private) }
-
- let(:guest_permissions) do
- [:read_label, :read_group, :upload_file, :read_namespace, :read_group_activity,
- :read_group_issues, :read_group_boards, :read_group_labels, :read_group_milestones,
- :read_group_merge_requests]
- end
-
- let(:reporter_permissions) { [:admin_label] }
-
- let(:developer_permissions) { [:admin_milestone] }
-
- let(:maintainer_permissions) do
- [
- :create_projects,
- :read_cluster,
- :create_cluster,
- :update_cluster,
- :admin_cluster,
- :add_cluster
- ]
- end
-
- let(:owner_permissions) do
- [
- :admin_group,
- :admin_namespace,
- :admin_group_member,
- :change_visibility_level,
- :set_note_created_at,
- (Gitlab::Database.postgresql? ? :create_subgroup : nil)
- ].compact
- end
-
- before do
- group.add_guest(guest)
- group.add_reporter(reporter)
- group.add_developer(developer)
- group.add_maintainer(maintainer)
- group.add_owner(owner)
- end
-
- subject { described_class.new(current_user, group) }
-
- def expect_allowed(*permissions)
- permissions.each { |p| is_expected.to be_allowed(p) }
- end
-
- def expect_disallowed(*permissions)
- permissions.each { |p| is_expected.not_to be_allowed(p) }
- end
+ include_context 'GroupPolicy context'
context 'with no user' do
let(:group) { create(:group, :public) }
diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb
index 1fdf95ad716..99fa8b1fe44 100644
--- a/spec/policies/namespace_policy_spec.rb
+++ b/spec/policies/namespace_policy_spec.rb
@@ -30,7 +30,7 @@ describe NamespacePolicy do
context 'user who has exceeded project limit' do
let(:owner) { create(:user, projects_limit: 0) }
- it { is_expected.not_to be_allowed(:create_projects) }
+ it { is_expected.to be_disallowed(:create_projects) }
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index c12c4677af1..125ed818bc6 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe ProjectPolicy do
+ include_context 'ProjectPolicy context'
set(:guest) { create(:user) }
set(:reporter) { create(:user) }
set(:developer) { create(:user) }
@@ -140,7 +141,7 @@ describe ProjectPolicy do
end
it 'disables boards and lists permissions' do
- expect_disallowed :read_board, :create_board, :update_board, :admin_board
+ expect_disallowed :read_board, :create_board, :update_board
expect_disallowed :read_list, :create_list, :update_list, :admin_list
end
@@ -237,237 +238,6 @@ describe ProjectPolicy do
end
end
- shared_examples 'archived project policies' do
- let(:feature_write_abilities) do
- described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature|
- described_class.create_update_admin_destroy(feature)
- end
- end
-
- let(:other_write_abilities) do
- %i[
- create_merge_request_in
- create_merge_request_from
- push_to_delete_protected_branch
- push_code
- request_access
- upload_file
- resolve_note
- award_emoji
- ]
- end
-
- context 'when the project is archived' do
- before do
- project.archived = true
- end
-
- it 'disables write actions on all relevant project features' do
- expect_disallowed(*feature_write_abilities)
- end
-
- it 'disables some other important write actions' do
- expect_disallowed(*other_write_abilities)
- end
-
- it 'does not disable other abilities' do
- expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities))
- end
- end
- end
-
- shared_examples 'project policies as anonymous' do
- context 'abilities for public projects' do
- context 'when a project has pending invites' do
- let(:group) { create(:group, :public) }
- let(:project) { create(:project, :public, namespace: group) }
- let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
- let(:anonymous_permissions) { guest_permissions - user_permissions }
-
- subject { described_class.new(nil, project) }
-
- before do
- create(:group_member, :invited, group: group)
- end
-
- it 'does not grant owner access' do
- expect_allowed(*anonymous_permissions)
- expect_disallowed(*user_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { anonymous_permissions }
- end
- end
- end
-
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(nil, project) }
-
- it { is_expected.to be_banned }
- end
- end
-
- shared_examples 'project policies as guest' do
- subject { described_class.new(guest, project) }
-
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
- let(:reporter_public_build_permissions) do
- reporter_permissions - [:read_build, :read_pipeline]
- end
-
- it do
- expect_allowed(*guest_permissions)
- expect_disallowed(*reporter_public_build_permissions)
- expect_disallowed(*team_member_reporter_permissions)
- expect_disallowed(*developer_permissions)
- expect_disallowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { guest_permissions }
- end
-
- context 'public builds enabled' do
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(:read_build, :read_pipeline)
- end
- end
-
- context 'when public builds disabled' do
- before do
- project.update(public_builds: false)
- end
-
- it do
- expect_allowed(*guest_permissions)
- expect_disallowed(:read_build, :read_pipeline)
- end
- end
-
- context 'when builds are disabled' do
- before do
- project.project_feature.update(builds_access_level: ProjectFeature::DISABLED)
- end
-
- it do
- expect_disallowed(:read_build)
- expect_allowed(:read_pipeline)
- end
- end
- end
- end
-
- shared_examples 'project policies as reporter' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(reporter, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*team_member_reporter_permissions)
- expect_disallowed(*developer_permissions)
- expect_disallowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { reporter_permissions }
- end
- end
- end
-
- shared_examples 'project policies as developer' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(developer, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*team_member_reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_disallowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { developer_permissions }
- end
- end
- end
-
- shared_examples 'project policies as maintainer' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(maintainer, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*team_member_reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_allowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { maintainer_permissions }
- end
- end
- end
-
- shared_examples 'project policies as owner' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(owner, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*team_member_reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_allowed(*maintainer_permissions)
- expect_allowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { owner_permissions }
- end
- end
- end
-
- shared_examples 'project policies as admin' do
- context 'abilities for non-public projects' do
- let(:project) { create(:project, namespace: owner.namespace) }
-
- subject { described_class.new(admin, project) }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_disallowed(*team_member_reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_allowed(*maintainer_permissions)
- expect_allowed(*owner_permissions)
- end
-
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { owner_permissions }
- end
- end
- end
-
it_behaves_like 'project policies as anonymous'
it_behaves_like 'project policies as guest'
it_behaves_like 'project policies as reporter'
diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb
index d6329e84579..2e9ef1e89fd 100644
--- a/spec/policies/project_snippet_policy_spec.rb
+++ b/spec/policies/project_snippet_policy_spec.rb
@@ -5,7 +5,7 @@ describe ProjectSnippetPolicy do
let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) }
let(:project) { create(:project, :public) }
-
+ let(:snippet) { create(:project_snippet, snippet_visibility, project: project) }
let(:author_permissions) do
[
:update_project_snippet,
@@ -13,23 +13,13 @@ describe ProjectSnippetPolicy do
]
end
- def abilities(user, snippet_visibility)
- snippet = create(:project_snippet, snippet_visibility, project: project)
-
- described_class.new(user, snippet)
- end
-
- def expect_allowed(*permissions)
- permissions.each { |p| is_expected.to be_allowed(p) }
- end
-
- def expect_disallowed(*permissions)
- permissions.each { |p| is_expected.not_to be_allowed(p) }
- end
+ subject { described_class.new(current_user, snippet) }
context 'public snippet' do
+ let(:snippet_visibility) { :public }
+
context 'no user' do
- subject { abilities(nil, :public) }
+ let(:current_user) { nil }
it do
expect_allowed(:read_project_snippet)
@@ -38,7 +28,7 @@ describe ProjectSnippetPolicy do
end
context 'regular user' do
- subject { abilities(regular_user, :public) }
+ let(:current_user) { regular_user }
it do
expect_allowed(:read_project_snippet, :create_note)
@@ -47,7 +37,7 @@ describe ProjectSnippetPolicy do
end
context 'external user' do
- subject { abilities(external_user, :public) }
+ let(:current_user) { external_user }
it do
expect_allowed(:read_project_snippet, :create_note)
@@ -57,8 +47,10 @@ describe ProjectSnippetPolicy do
end
context 'internal snippet' do
+ let(:snippet_visibility) { :internal }
+
context 'no user' do
- subject { abilities(nil, :internal) }
+ let(:current_user) { nil }
it do
expect_disallowed(:read_project_snippet)
@@ -67,7 +59,7 @@ describe ProjectSnippetPolicy do
end
context 'regular user' do
- subject { abilities(regular_user, :internal) }
+ let(:current_user) { regular_user }
it do
expect_allowed(:read_project_snippet, :create_note)
@@ -76,31 +68,31 @@ describe ProjectSnippetPolicy do
end
context 'external user' do
- subject { abilities(external_user, :internal) }
+ let(:current_user) { external_user }
it do
expect_disallowed(:read_project_snippet, :create_note)
expect_disallowed(*author_permissions)
end
- end
- context 'project team member external user' do
- subject { abilities(external_user, :internal) }
-
- before do
- project.add_developer(external_user)
- end
+ context 'project team member' do
+ before do
+ project.add_developer(external_user)
+ end
- it do
- expect_allowed(:read_project_snippet, :create_note)
- expect_disallowed(*author_permissions)
+ it do
+ expect_allowed(:read_project_snippet, :create_note)
+ expect_disallowed(*author_permissions)
+ end
end
end
end
context 'private snippet' do
+ let(:snippet_visibility) { :private }
+
context 'no user' do
- subject { abilities(nil, :private) }
+ let(:current_user) { nil }
it do
expect_disallowed(:read_project_snippet)
@@ -109,53 +101,52 @@ describe ProjectSnippetPolicy do
end
context 'regular user' do
- subject { abilities(regular_user, :private) }
+ let(:current_user) { regular_user }
it do
expect_disallowed(:read_project_snippet, :create_note)
expect_disallowed(*author_permissions)
end
- end
-
- context 'snippet author' do
- let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
- subject { described_class.new(regular_user, snippet) }
+ context 'snippet author' do
+ let(:snippet) { create(:project_snippet, :private, author: regular_user, project: project) }
- it do
- expect_allowed(:read_project_snippet, :create_note)
- expect_allowed(*author_permissions)
+ it do
+ expect_allowed(:read_project_snippet, :create_note)
+ expect_allowed(*author_permissions)
+ end
end
- end
- context 'project team member normal user' do
- subject { abilities(regular_user, :private) }
-
- before do
- project.add_developer(regular_user)
- end
+ context 'project team member normal user' do
+ before do
+ project.add_developer(regular_user)
+ end
- it do
- expect_allowed(:read_project_snippet, :create_note)
- expect_disallowed(*author_permissions)
+ it do
+ expect_allowed(:read_project_snippet, :create_note)
+ expect_disallowed(*author_permissions)
+ end
end
end
- context 'project team member external user' do
- subject { abilities(external_user, :private) }
+ context 'external user' do
+ context 'project team member' do
+ let(:current_user) { external_user }
- before do
- project.add_developer(external_user)
- end
+ before do
+ project.add_developer(external_user)
+ end
- it do
- expect_allowed(:read_project_snippet, :create_note)
- expect_disallowed(*author_permissions)
+ it do
+ expect_allowed(:read_project_snippet, :create_note)
+ expect_disallowed(*author_permissions)
+ end
end
end
context 'admin user' do
- subject { abilities(create(:admin), :private) }
+ let(:snippet_visibility) { :private }
+ let(:current_user) { create(:admin) }
it do
expect_allowed(:read_project_snippet, :create_note)
diff --git a/spec/presenters/ci/build_runner_presenter_spec.rb b/spec/presenters/ci/build_runner_presenter_spec.rb
index f50bcf54b46..ad6cb012d0b 100644
--- a/spec/presenters/ci/build_runner_presenter_spec.rb
+++ b/spec/presenters/ci/build_runner_presenter_spec.rb
@@ -136,6 +136,24 @@ describe Ci::BuildRunnerPresenter do
is_expected.to eq(1)
end
end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.first }
+ let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) }
+
+ it 'returns the default git depth for pipelines for merge requests' do
+ is_expected.to eq(described_class::DEFAULT_GIT_DEPTH_MERGE_REQUEST)
+ end
+
+ context 'when pipeline is legacy detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) }
+
+ it 'behaves as branch pipeline' do
+ is_expected.to eq(0)
+ end
+ end
+ end
end
describe '#refspecs' do
@@ -165,5 +183,25 @@ describe Ci::BuildRunnerPresenter do
end
end
end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.first }
+ let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) }
+
+ it 'returns the correct refspecs' do
+ is_expected
+ .to contain_exactly('+refs/merge-requests/1/head:refs/merge-requests/1/head')
+ end
+
+ context 'when pipeline is legacy detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) }
+
+ it 'returns the correct refspecs' do
+ is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
+ '+refs/heads/*:refs/remotes/origin/*')
+ end
+ end
+ end
end
end
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 754ba0a594c..a9d786bc872 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -228,4 +228,20 @@ describe Clusters::ClusterPresenter do
it { is_expected.to eq(group_cluster_path(group, cluster)) }
end
end
+
+ describe '#read_only_kubernetes_platform_fields?' do
+ subject { described_class.new(cluster).read_only_kubernetes_platform_fields? }
+
+ context 'with a user-provided cluster' do
+ let(:cluster) { build_stubbed(:cluster, :provided_by_user) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with a GCP-provided cluster' do
+ let(:cluster) { build_stubbed(:cluster, :provided_by_gcp) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index b38cd66986f..8b503777443 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -20,9 +20,9 @@ describe API::Branches do
let(:route) { "/projects/#{project_id}/repository/branches" }
shared_examples_for 'repository branches' do
- RSpec::Matchers.define :has_merged_branch_names_count do |expected|
+ RSpec::Matchers.define :has_up_to_merged_branch_names_count do |expected|
match do |actual|
- actual[:merged_branch_names].count == expected
+ expected >= actual[:merged_branch_names].count
end
end
@@ -36,10 +36,30 @@ describe API::Branches do
expect(branch_names).to match_array(project.repository.branch_names)
end
+ def check_merge_status(json_response)
+ merged, unmerged = json_response.partition { |branch| branch['merged'] }
+ merged_branches = merged.map { |branch| branch['name'] }
+ unmerged_branches = unmerged.map { |branch| branch['name'] }
+ expect(Set.new(merged_branches)).to eq(project.repository.merged_branch_names(merged_branches + unmerged_branches))
+ expect(project.repository.merged_branch_names(unmerged_branches)).to be_empty
+ end
+
it 'determines only a limited number of merged branch names' do
- expect(API::Entities::Branch).to receive(:represent).with(anything, has_merged_branch_names_count(2))
+ expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
get api(route, current_user), params: { per_page: 2 }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ check_merge_status(json_response)
+ end
+
+ it 'merge status matches reality on paginated input' do
+ get api(route, current_user), params: { per_page: 20, page: 2 }
+
+ expect(response).to have_gitlab_http_status(200)
+
+ check_merge_status(json_response)
end
context 'when repository is disabled' do
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index deb6abbc026..74820d39102 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -70,13 +70,13 @@ describe 'getting merge request information nested in a project' do
context 'when there are pipelines' do
before do
- pipeline = create(
+ create(
:ci_pipeline,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha
)
- merge_request.update!(head_pipeline: pipeline)
+ merge_request.update_head_pipeline
end
it 'has a head pipeline' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index b184c92824a..537194b8e11 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -321,7 +321,7 @@ describe API::Internal do
end
context 'with env passed as a JSON' do
- let(:gl_repository) { project.gl_repository(is_wiki: true) }
+ let(:gl_repository) { Gitlab::GlRepository::WIKI.identifier_for_subject(project) }
it 'sets env in RequestStore' do
obj_dir_relative = './objects'
@@ -975,9 +975,9 @@ describe API::Internal do
def gl_repository_for(project_or_wiki)
case project_or_wiki
when ProjectWiki
- project_or_wiki.project.gl_repository(is_wiki: true)
+ Gitlab::GlRepository::WIKI.identifier_for_subject(project_or_wiki.project)
when Project
- project_or_wiki.gl_repository(is_wiki: false)
+ Gitlab::GlRepository::PROJECT.identifier_for_subject(project_or_wiki)
else
nil
end
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index 4e42e233b4c..81442125a1c 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -60,7 +60,7 @@ describe API::ProjectClusters do
end
let(:cluster) do
- create(:cluster, :project, :provided_by_gcp,
+ create(:cluster, :project, :provided_by_gcp, :with_domain,
platform_kubernetes: platform_kubernetes,
user: current_user,
projects: [project])
@@ -88,6 +88,7 @@ describe API::ProjectClusters do
expect(json_response['platform_type']).to eq('kubernetes')
expect(json_response['environment_scope']).to eq('*')
expect(json_response['cluster_type']).to eq('project_type')
+ expect(json_response['domain']).to eq('example.com')
end
it 'returns project information' do
@@ -187,6 +188,7 @@ describe API::ProjectClusters do
let(:cluster_params) do
{
name: 'test-cluster',
+ domain: 'domain.example.com',
platform_kubernetes_attributes: platform_kubernetes_attributes
}
end
@@ -217,6 +219,7 @@ describe API::ProjectClusters do
expect(cluster_result).to be_kubernetes
expect(cluster_result.project).to eq(project)
expect(cluster_result.name).to eq('test-cluster')
+ expect(cluster_result.domain).to eq('domain.example.com')
expect(platform_kubernetes.rbac?).to be_truthy
expect(platform_kubernetes.api_url).to eq(api_url)
expect(platform_kubernetes.namespace).to eq(namespace)
@@ -294,6 +297,7 @@ describe API::ProjectClusters do
let(:update_params) do
{
+ domain: 'new-domain.com',
platform_kubernetes_attributes: platform_kubernetes_attributes
}
end
@@ -330,6 +334,7 @@ describe API::ProjectClusters do
end
it 'should update cluster attributes' do
+ expect(cluster.domain).to eq('new-domain.com')
expect(cluster.platform_kubernetes.namespace).to eq('new-namespace')
end
end
@@ -342,6 +347,7 @@ describe API::ProjectClusters do
end
it 'should not update cluster attributes' do
+ expect(cluster.domain).not_to eq('new_domain.com')
expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace')
expect(cluster.kubernetes_namespace.namespace).not_to eq('invalid_namespace')
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fdbb78b8829..2bfb17d9c9a 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1189,6 +1189,16 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(200)
expect(json_response).to include 'statistics'
end
+
+ it "includes statistics also when repository is disabled" do
+ project.add_developer(user)
+ project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
+
+ get api("/projects/#{project.id}", user), params: { statistics: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to include 'statistics'
+ end
end
it "includes import_error if user can admin project" do
diff --git a/spec/requests/api/suggestions_spec.rb b/spec/requests/api/suggestions_spec.rb
index 3c2842e5725..5b07e598b8d 100644
--- a/spec/requests/api/suggestions_spec.rb
+++ b/spec/requests/api/suggestions_spec.rb
@@ -42,8 +42,7 @@ describe API::Suggestions do
expect(response).to have_gitlab_http_status(200)
expect(json_response)
- .to include('id', 'from_original_line', 'to_original_line',
- 'from_line', 'to_line', 'appliable', 'applied',
+ .to include('id', 'from_line', 'to_line', 'appliable', 'applied',
'from_content', 'to_content')
end
end
diff --git a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
index 1c8ab0ad5d2..cba01400d85 100644
--- a/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
+++ b/spec/rubocop/cop/migration/update_column_in_batches_spec.rb
@@ -93,4 +93,22 @@ describe RuboCop::Cop::Migration::UpdateColumnInBatches do
it_behaves_like 'a migration file with no spec file'
it_behaves_like 'a migration file with a spec file'
end
+
+ context 'EE migrations' do
+ let(:spec_filepath) { tmp_rails_root.join('ee', 'spec', 'migrations', 'my_super_migration_spec.rb') }
+
+ context 'in a migration' do
+ let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'migrate', '20121220064453_my_super_migration.rb') }
+
+ it_behaves_like 'a migration file with no spec file'
+ it_behaves_like 'a migration file with a spec file'
+ end
+
+ context 'in a post migration' do
+ let(:migration_filepath) { tmp_rails_root.join('ee', 'db', 'post_migrate', '20121220064453_my_super_migration.rb') }
+
+ it_behaves_like 'a migration file with no spec file'
+ it_behaves_like 'a migration file with a spec file'
+ end
+ end
end
diff --git a/spec/serializers/merge_request_widget_entity_spec.rb b/spec/serializers/merge_request_widget_entity_spec.rb
index 4dbd79f2fc0..727fd8951f2 100644
--- a/spec/serializers/merge_request_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_widget_entity_spec.rb
@@ -279,13 +279,18 @@ describe MergeRequestWidgetEntity do
end
describe 'commits_without_merge_commits' do
+ def find_matching_commit(short_id)
+ resource.commits.find { |c| c.short_id == short_id }
+ end
+
it 'should not include merge commits' do
- # Mock all but the first 5 commits to be merge commits
- resource.commits.each_with_index do |commit, i|
- expect(commit).to receive(:merge_commit?).at_least(:once).and_return(i > 4)
- end
+ commits_in_widget = subject[:commits_without_merge_commits]
- expect(subject[:commits_without_merge_commits].size).to eq(5)
+ expect(commits_in_widget.length).to be < resource.commits.length
+ expect(commits_in_widget.length).to eq(resource.commits.without_merge_commits.length)
+ commits_in_widget.each do |c|
+ expect(find_matching_commit(c[:short_id]).merge_commit?).to eq(false)
+ end
end
end
end
diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb
index 047571f161c..d38fc2b132b 100644
--- a/spec/serializers/suggestion_entity_spec.rb
+++ b/spec/serializers/suggestion_entity_spec.rb
@@ -13,8 +13,8 @@ describe SuggestionEntity do
subject { entity.as_json }
it 'exposes correct attributes' do
- expect(subject).to include(:id, :from_original_line, :to_original_line, :from_line,
- :to_line, :appliable, :applied, :from_content, :to_content)
+ expect(subject).to include(:id, :from_line, :to_line, :appliable,
+ :applied, :from_content, :to_content)
end
it 'exposes current user abilities' do
diff --git a/spec/services/ci/destroy_pipeline_service_spec.rb b/spec/services/ci/destroy_pipeline_service_spec.rb
index d896f990470..bff2b3179fb 100644
--- a/spec/services/ci/destroy_pipeline_service_spec.rb
+++ b/spec/services/ci/destroy_pipeline_service_spec.rb
@@ -3,8 +3,8 @@
require 'spec_helper'
describe ::Ci::DestroyPipelineService do
- let(:project) { create(:project) }
- let!(:pipeline) { create(:ci_pipeline, project: project) }
+ let(:project) { create(:project, :repository) }
+ let!(:pipeline) { create(:ci_pipeline, :success, project: project, sha: project.commit.id) }
subject { described_class.new(project, user).execute(pipeline) }
@@ -17,6 +17,17 @@ describe ::Ci::DestroyPipelineService do
expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
+ it 'clears the cache', :use_clean_rails_memory_store_caching do
+ create(:commit_status, :success, pipeline: pipeline, ref: pipeline.ref)
+
+ expect(project.pipeline_status.has_status?).to be_truthy
+
+ subject
+
+ # Need to use find to avoid memoization
+ expect(Project.find(project.id).pipeline_status.has_status?).to be_falsey
+ end
+
it 'does not log an audit event' do
expect { subject }.not_to change { SecurityEvent.count }
end
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index cbdef008b07..20555873503 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -150,7 +150,7 @@ describe Clusters::Applications::CreateService do
where(:application, :association, :allowed, :pre_create_helm) do
'helm' | :application_helm | true | false
'ingress' | :application_ingress | true | true
- 'runner' | :application_runner | false | true
+ 'runner' | :application_runner | true | true
'jupyter' | :application_jupyter | false | true
'prometheus' | :application_prometheus | false | true
end
diff --git a/spec/services/error_tracking/list_issues_service_spec.rb b/spec/services/error_tracking/list_issues_service_spec.rb
index 9d4fc62f923..3a8f3069911 100644
--- a/spec/services/error_tracking/list_issues_service_spec.rb
+++ b/spec/services/error_tracking/list_issues_service_spec.rb
@@ -53,7 +53,10 @@ describe ErrorTracking::ListIssuesService do
before do
allow(error_tracking_setting)
.to receive(:list_sentry_issues)
- .and_return(error: 'Sentry response status code: 401')
+ .and_return(
+ error: 'Sentry response status code: 401',
+ error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE
+ )
end
it 'returns the error' do
@@ -64,6 +67,25 @@ describe ErrorTracking::ListIssuesService do
)
end
end
+
+ context 'when list_sentry_issues returns error with http_status' do
+ before do
+ allow(error_tracking_setting)
+ .to receive(:list_sentry_issues)
+ .and_return(
+ error: 'Sentry API response is missing keys. key not found: "id"',
+ error_type: ErrorTracking::ProjectErrorTrackingSetting::SENTRY_API_ERROR_TYPE_MISSING_KEYS
+ )
+ end
+
+ it 'returns the error with correct http_status' do
+ expect(result).to eq(
+ status: :error,
+ http_status: :internal_server_error,
+ message: 'Sentry API response is missing keys. key not found: "id"'
+ )
+ end
+ end
end
context 'with unauthorized user' do
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index e8fce951155..d0e2169b4a6 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GitPushService, services: true do
+describe Git::BranchPushService, services: true do
include RepoHelpers
set(:user) { create(:user) }
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb
index 2699f6e7bcd..e151db5827f 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git/tag_push_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe GitTagPushService do
+describe Git::TagPushService do
include RepoHelpers
include GitHelpers
diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb
index 715b1168bfb..d50412b6d2c 100644
--- a/spec/services/issues/create_service_spec.rb
+++ b/spec/services/issues/create_service_spec.rb
@@ -138,6 +138,20 @@ describe Issues::CreateService do
end
end
+ context 'when duplicate label titles are given' do
+ let(:label) { create(:label, project: project) }
+
+ let(:opts) do
+ { title: 'Title',
+ description: 'Description',
+ labels: [label.title, label.title] }
+ end
+
+ it 'assigns the label once' do
+ expect(issue.labels).to contain_exactly(label)
+ end
+ end
+
it 'executes issue hooks when issue is not confidential' do
opts = { title: 'Title', description: 'Description', confidential: false }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index f1684209729..1c8a4b608d5 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -592,6 +592,16 @@ describe Issues::UpdateService, :mailer do
expect(result.label_ids).not_to include(label.id)
end
end
+
+ context 'when duplicate label titles are given' do
+ let(:params) do
+ { labels: [label3.title, label3.title] }
+ end
+
+ it 'assigns the label once' do
+ expect(result.labels).to contain_exactly(label3)
+ end
+ end
end
context 'updating asssignee_id' do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index a04a4d5fc36..55e7b46248b 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -173,7 +173,7 @@ describe MergeRequests::CreateService do
end
end
- describe 'Merge request pipelines' do
+ describe 'Pipelines for merge requests' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
@@ -189,12 +189,46 @@ describe MergeRequests::CreateService do
}
end
- it 'creates a merge request pipeline and sets it as a head pipeline' do
+ it 'creates a detached merge request pipeline and sets it as a head pipeline' do
expect(merge_request).to be_persisted
merge_request.reload
expect(merge_request.merge_request_pipelines.count).to eq(1)
- expect(merge_request.actual_head_pipeline).to be_merge_request_event
+ expect(merge_request.actual_head_pipeline).to be_detached_merge_request_pipeline
+ end
+
+ context 'when merge request is submitted from forked project' do
+ let(:target_project) { fork_project(project, nil, repository: true) }
+
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ source_branch: 'feature',
+ target_branch: 'master',
+ target_project_id: target_project.id
+ }
+ end
+
+ before do
+ target_project.add_developer(assignee)
+ target_project.add_maintainer(user)
+ end
+
+ it 'create legacy detached merge request pipeline for fork merge request' do
+ expect(merge_request.actual_head_pipeline)
+ .to be_legacy_detached_merge_request_pipeline
+ end
+ end
+
+ context 'when ci_use_merge_request_ref feature flag is false' do
+ before do
+ stub_feature_flags(ci_use_merge_request_ref: false)
+ end
+
+ it 'create legacy detached merge request pipeline for non-fork merge request' do
+ expect(merge_request.actual_head_pipeline)
+ .to be_legacy_detached_merge_request_pipeline
+ end
end
context 'when there are no commits between source branch and target branch' do
@@ -207,7 +241,7 @@ describe MergeRequests::CreateService do
}
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(merge_request).to be_persisted
merge_request.reload
@@ -225,7 +259,7 @@ describe MergeRequests::CreateService do
merge_request
end
- it 'sets the latest merge request pipeline as the head pipeline' do
+ it 'sets the latest detached merge request pipeline as the head pipeline' do
expect(merge_request.actual_head_pipeline).to be_merge_request_event
end
end
@@ -235,7 +269,7 @@ describe MergeRequests::CreateService do
stub_feature_flags(ci_merge_request_pipeline: false)
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(merge_request).to be_persisted
merge_request.reload
@@ -254,7 +288,7 @@ describe MergeRequests::CreateService do
}
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect(merge_request).to be_persisted
merge_request.reload
diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb
index fe673de46aa..1430e12a07e 100644
--- a/spec/services/merge_requests/ff_merge_service_spec.rb
+++ b/spec/services/merge_requests/ff_merge_service_spec.rb
@@ -72,7 +72,7 @@ describe MergeRequests::FfMergeService do
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
- allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message)
+ allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}")
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index ede79b87bcc..887ec17171e 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -239,7 +239,7 @@ describe MergeRequests::MergeService do
it 'logs and saves error if there is an PreReceiveError exception' do
error_message = 'error message'
- allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, error_message)
+ allow(service).to receive(:repository).and_raise(Gitlab::Git::PreReceiveError, "GitLab: #{error_message}")
allow(service).to receive(:execute_hooks)
service.execute(merge_request)
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 fabca8f6b4a..a3b48abae26 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -40,15 +40,6 @@ describe MergeRequests::MergeToRefService do
end
shared_examples_for 'successfully evaluates pre-condition checks' do
- it 'returns error when feature is disabled' do
- stub_feature_flags(merge_to_tmp_merge_ref_path: false)
-
- result = service.execute(merge_request)
-
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq('Feature is not enabled')
- end
-
it 'returns an error when the failing to process the merge' do
allow(project.repository).to receive(:merge_to_ref).and_return(nil)
@@ -180,6 +171,17 @@ describe MergeRequests::MergeToRefService do
it { expect(todo).not_to be_done }
end
+ context 'when merge request is WIP state' do
+ it 'fails to merge' do
+ merge_request = create(:merge_request, title: 'WIP: The feature')
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}")
+ end
+ end
+
it 'returns error when user has no authorization to admin the merge request' do
unauthorized_user = create(:user)
project.add_reporter(unauthorized_user)
diff --git a/spec/services/merge_requests/migrate_external_diffs_service_spec.rb b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
new file mode 100644
index 00000000000..40ac747e66f
--- /dev/null
+++ b/spec/services/merge_requests/migrate_external_diffs_service_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequests::MigrateExternalDiffsService do
+ let(:merge_request) { create(:merge_request) }
+ let(:diff) { merge_request.merge_request_diff }
+
+ describe '.enqueue!', :sidekiq do
+ around do |example|
+ Sidekiq::Testing.fake! { example.run }
+ end
+
+ it 'enqueues nothing if external diffs are disabled' do
+ expect(diff).not_to be_stored_externally
+
+ expect { described_class.enqueue! }
+ .not_to change { MigrateExternalDiffsWorker.jobs.count }
+ end
+
+ it 'enqueues eligible in-database diffs if external diffs are enabled' do
+ expect(diff).not_to be_stored_externally
+
+ stub_external_diffs_setting(enabled: true)
+
+ expect { described_class.enqueue! }
+ .to change { MigrateExternalDiffsWorker.jobs.count }
+ .by(1)
+ end
+ end
+
+ describe '#execute' do
+ it 'migrates an in-database diff to the external store' do
+ expect(diff).not_to be_stored_externally
+
+ stub_external_diffs_setting(enabled: true)
+
+ described_class.new(diff).execute
+
+ expect(diff).to be_stored_externally
+ end
+ end
+end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 43ceb1dcbee..25cbac6d7ee 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -97,6 +97,15 @@ describe MergeRequests::RefreshService do
}
end
+ it 'outdates MR suggestions' do
+ expect_next_instance_of(Suggestions::OutdateService) do |service|
+ expect(service).to receive(:execute).with(@merge_request).and_call_original
+ expect(service).to receive(:execute).with(@another_merge_request).and_call_original
+ end
+
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ end
+
context 'when source branch ref does not exists' do
before do
DeleteBranchService.new(@project, @user).execute(@merge_request.source_branch)
@@ -132,7 +141,7 @@ describe MergeRequests::RefreshService do
end
end
- describe 'Merge request pipelines' do
+ describe 'Pipelines for merge requests' do
before do
stub_ci_pipeline_yaml_file(YAML.dump(config))
end
@@ -150,7 +159,7 @@ describe MergeRequests::RefreshService do
}
end
- it 'create merge request pipeline with commits' do
+ it 'create detached merge request pipeline with commits' do
expect { subject }
.to change { @merge_request.merge_request_pipelines.count }.by(1)
.and change { @fork_merge_request.merge_request_pipelines.count }.by(1)
@@ -161,7 +170,34 @@ describe MergeRequests::RefreshService do
expect(@another_merge_request.has_commits?).to be_falsy
end
- context "when branch pipeline was created before a merge request pipline has been created" do
+ it 'create detached merge request pipeline for non-fork merge request' do
+ subject
+
+ expect(@merge_request.merge_request_pipelines.first)
+ .to be_detached_merge_request_pipeline
+ end
+
+ it 'create legacy detached merge request pipeline for fork merge request' do
+ subject
+
+ expect(@fork_merge_request.merge_request_pipelines.first)
+ .to be_legacy_detached_merge_request_pipeline
+ end
+
+ context 'when ci_use_merge_request_ref feature flag is false' do
+ before do
+ stub_feature_flags(ci_use_merge_request_ref: false)
+ end
+
+ it 'create legacy detached merge request pipeline for non-fork merge request' do
+ subject
+
+ expect(@merge_request.merge_request_pipelines.first)
+ .to be_legacy_detached_merge_request_pipeline
+ end
+ end
+
+ context "when branch pipeline was created before a detaced merge request pipeline has been created" do
before do
create(:ci_pipeline, project: @merge_request.source_project,
sha: @merge_request.diff_head_sha,
@@ -171,7 +207,7 @@ describe MergeRequests::RefreshService do
subject
end
- it 'sets the latest merge request pipeline as a head pipeline' do
+ it 'sets the latest detached merge request pipeline as a head pipeline' do
@merge_request.reload
expect(@merge_request.actual_head_pipeline).to be_merge_request_event
end
@@ -184,7 +220,7 @@ describe MergeRequests::RefreshService do
end
context "when MergeRequestUpdateWorker is retried by an exception" do
- it 'does not re-create a duplicate merge request pipeline' do
+ it 'does not re-create a duplicate detached merge request pipeline' do
expect do
service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
end.to change { @merge_request.merge_request_pipelines.count }.by(1)
@@ -200,7 +236,7 @@ describe MergeRequests::RefreshService do
stub_feature_flags(ci_merge_request_pipeline: false)
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect { subject }
.not_to change { @merge_request.merge_request_pipelines.count }
end
@@ -217,7 +253,7 @@ describe MergeRequests::RefreshService do
}
end
- it 'does not create a merge request pipeline' do
+ it 'does not create a detached merge request pipeline' do
expect { subject }
.not_to change { @merge_request.merge_request_pipelines.count }
end
@@ -329,14 +365,16 @@ describe MergeRequests::RefreshService do
context 'push to fork repo source branch' do
let(:refresh_service) { service.new(@fork_project, @user) }
- context 'open fork merge request' do
- before do
- allow(refresh_service).to receive(:execute_hooks)
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
- reload_mrs
- end
+ def refresh
+ allow(refresh_service).to receive(:execute_hooks)
+ refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+ context 'open fork merge request' do
it 'executes hooks with update action' do
+ refresh
+
expect(refresh_service).to have_received(:execute_hooks)
.with(@fork_merge_request, 'update', old_rev: @oldrev)
@@ -347,21 +385,30 @@ describe MergeRequests::RefreshService do
expect(@build_failed_todo).to be_pending
expect(@fork_build_failed_todo).to be_pending
end
+
+ it 'outdates opened forked MR suggestions' do
+ expect_next_instance_of(Suggestions::OutdateService) do |service|
+ expect(service).to receive(:execute).with(@fork_merge_request).and_call_original
+ end
+
+ refresh
+ end
end
context 'closed fork merge request' do
before do
@fork_merge_request.close!
- allow(refresh_service).to receive(:execute_hooks)
- refresh_service.execute(@oldrev, @newrev, 'refs/heads/master')
- reload_mrs
end
it 'do not execute hooks with update action' do
+ refresh
+
expect(refresh_service).not_to have_received(:execute_hooks)
end
it 'updates merge request to closed state' do
+ refresh
+
expect(@merge_request.notes).to be_empty
expect(@merge_request).to be_open
expect(@fork_merge_request.notes).to be_empty
diff --git a/spec/services/projects/after_import_service_spec.rb b/spec/services/projects/after_import_service_spec.rb
index 4dd6c6dab86..765b4ffae8f 100644
--- a/spec/services/projects/after_import_service_spec.rb
+++ b/spec/services/projects/after_import_service_spec.rb
@@ -13,7 +13,7 @@ describe Projects::AfterImportService do
describe '#execute' do
before do
allow(Projects::HousekeepingService)
- .to receive(:new).with(project).and_return(housekeeping_service)
+ .to receive(:new).with(project, :gc).and_return(housekeeping_service)
allow(housekeeping_service)
.to receive(:execute).and_yield
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index 18ecef1c0a1..12ae9105627 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -88,6 +88,19 @@ describe Projects::HousekeepingService do
expect(project.pushes_since_gc).to eq(1)
end
end
+
+ it 'runs the task specifically requested' do
+ housekeeping = described_class.new(project, :gc)
+
+ allow(housekeeping).to receive(:try_obtain_lease).and_return(:gc_uuid)
+ allow(housekeeping).to receive(:lease_key).and_return(:gc_lease_key)
+
+ expect(GitGarbageCollectWorker).to receive(:perform_async).with(project.id, :gc, :gc_lease_key, :gc_uuid).twice
+
+ 2.times do
+ housekeeping.execute
+ end
+ end
end
describe '#needed?' do
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
index 6040f9100f8..4b6d0c51363 100644
--- a/spec/services/projects/participants_service_spec.rb
+++ b/spec/services/projects/participants_service_spec.rb
@@ -2,29 +2,56 @@ require 'spec_helper'
describe Projects::ParticipantsService do
describe '#groups' do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :public) }
+ let(:service) { described_class.new(project, user) }
+
+ it 'avoids N+1 queries' do
+ group_1 = create(:group)
+ group_1.add_owner(user)
+
+ service.groups # Run general application warmup queries
+ control_count = ActiveRecord::QueryRecorder.new { service.groups }.count
+
+ group_2 = create(:group)
+ group_2.add_owner(user)
+
+ expect { service.groups }.not_to exceed_query_limit(control_count)
+ end
+
+ it 'returns correct user counts for groups' do
+ group_1 = create(:group)
+ group_1.add_owner(user)
+ group_1.add_owner(create(:user))
+
+ group_2 = create(:group)
+ group_2.add_owner(user)
+ create(:group_member, :access_request, group: group_2, user: create(:user))
+
+ expect(service.groups).to contain_exactly(
+ a_hash_including(name: group_1.full_name, count: 2),
+ a_hash_including(name: group_2.full_name, count: 1)
+ )
+ end
+
describe 'avatar_url' do
- let(:project) { create(:project, :public) }
let(:group) { create(:group, avatar: fixture_file_upload('spec/fixtures/dk.png')) }
- let(:user) { create(:user) }
- let!(:group_member) { create(:group_member, group: group, user: user) }
- it 'should return an url for the avatar' do
- participants = described_class.new(project, user)
- groups = participants.groups
+ before do
+ group.add_owner(user)
+ end
- expect(groups.size).to eq 1
- expect(groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png")
+ it 'should return an url for the avatar' do
+ expect(service.groups.size).to eq 1
+ expect(service.groups.first[:avatar_url]).to eq("/uploads/-/system/group/avatar/#{group.id}/dk.png")
end
it 'should return an url for the avatar with relative url' do
stub_config_setting(relative_url_root: '/gitlab')
stub_config_setting(url: Settings.send(:build_gitlab_url))
- participants = described_class.new(project, user)
- groups = participants.groups
-
- expect(groups.size).to eq 1
- expect(groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png")
+ expect(service.groups.size).to eq 1
+ expect(service.groups.first[:avatar_url]).to eq("/gitlab/uploads/-/system/group/avatar/#{group.id}/dk.png")
end
end
end
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index ea33d156c8a..8b0f9c8ade2 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -15,6 +15,7 @@ describe QuickActions::InterpretService do
let(:service) { described_class.new(project, developer) }
before do
+ stub_licensed_features(multiple_issue_assignees: false)
project.add_developer(developer)
end
diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb
index dd5b8708f36..28663ca8853 100644
--- a/spec/services/releases/destroy_service_spec.rb
+++ b/spec/services/releases/destroy_service_spec.rb
@@ -28,13 +28,11 @@ describe Releases::DestroyService do
end
end
- context 'when tag is not found' do
+ context 'when tag does not exist in the repository' do
let(:tag) { 'v1.1.1' }
- it 'returns an error' do
- is_expected.to include(status: :error,
- message: 'Tag does not exist',
- http_status: 404)
+ it 'removes the orphaned release' do
+ expect { subject }.to change { project.releases.count }.by(-1)
end
end
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index fe85b5c9065..80b5dcac6c7 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -5,6 +5,41 @@ require 'spec_helper'
describe Suggestions::ApplyService do
include ProjectForksHelper
+ shared_examples 'successfully creates commit and updates suggestion' do
+ def apply(suggestion)
+ result = subject.execute(suggestion)
+ expect(result[:status]).to eq(:success)
+ end
+
+ it 'updates the file with the new contents' do
+ apply(suggestion)
+
+ blob = project.repository.blob_at_branch(merge_request.source_branch,
+ position.new_path)
+
+ expect(blob.data).to eq(expected_content)
+ end
+
+ it 'updates suggestion applied and commit_id columns' do
+ expect { apply(suggestion) }
+ .to change(suggestion, :applied)
+ .from(false).to(true)
+ .and change(suggestion, :commit_id)
+ .from(nil)
+ end
+
+ it 'created commit has users email and name' do
+ apply(suggestion)
+
+ commit = project.repository.commit
+
+ expect(user.commit_email).not_to eq(user.email)
+ expect(commit.author_email).to eq(user.commit_email)
+ expect(commit.committer_email).to eq(user.commit_email)
+ expect(commit.author_name).to eq(user.name)
+ end
+ end
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user, :commit_email) }
@@ -17,9 +52,8 @@ describe Suggestions::ApplyService do
end
let(:suggestion) do
- create(:suggestion, note: diff_note,
- from_content: " raise RuntimeError, \"System commands must be given as an array of strings\"\n",
- to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n")
+ create(:suggestion, :content_from_repo, note: diff_note,
+ to_content: " raise RuntimeError, 'Explosion'\n # explosion?\n")
end
subject { described_class.new(user) }
@@ -84,39 +118,7 @@ describe Suggestions::ApplyService do
project.add_maintainer(user)
end
- it 'updates the file with the new contents' do
- subject.execute(suggestion)
-
- blob = project.repository.blob_at_branch(merge_request.source_branch,
- position.new_path)
-
- expect(blob.data).to eq(expected_content)
- end
-
- it 'returns success status' do
- result = subject.execute(suggestion)
-
- expect(result[:status]).to eq(:success)
- end
-
- it 'updates suggestion applied and commit_id columns' do
- expect { subject.execute(suggestion) }
- .to change(suggestion, :applied)
- .from(false).to(true)
- .and change(suggestion, :commit_id)
- .from(nil)
- end
-
- it 'created commit has users email and name' do
- subject.execute(suggestion)
-
- commit = project.repository.commit
-
- expect(user.commit_email).not_to eq(user.email)
- expect(commit.author_email).to eq(user.commit_email)
- expect(commit.committer_email).to eq(user.commit_email)
- expect(commit.author_name).to eq(user.name)
- end
+ it_behaves_like 'successfully creates commit and updates suggestion'
context 'when it fails to apply because the file was changed' do
it 'returns error message' do
@@ -212,11 +214,13 @@ describe Suggestions::ApplyService do
end
def apply_suggestion(suggestion)
- suggestion.note.reload
+ suggestion.reload
merge_request.reload
merge_request.clear_memoized_shas
result = subject.execute(suggestion)
+ expect(result[:status]).to eq(:success)
+
refresh = MergeRequests::RefreshService.new(project, user)
refresh.execute(merge_request.diff_head_sha,
suggestion.commit_id,
@@ -241,7 +245,7 @@ describe Suggestions::ApplyService do
suggestion_2_changes = { old_line: 24,
new_line: 31,
- from_content: " @cmd_output << stderr.read\n",
+ from_content: " @cmd_output << stderr.read\n",
to_content: "# v2 change\n",
path: path }
@@ -368,7 +372,18 @@ describe Suggestions::ApplyService do
result = subject.execute(suggestion)
- expect(result).to eq(message: 'The file was not found',
+ expect(result).to eq(message: 'Suggestion is not appliable',
+ status: :error)
+ end
+ end
+
+ context 'suggestion is eligible to be outdated' do
+ it 'returns error message' do
+ expect(suggestion).to receive(:outdated?) { true }
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'Suggestion is not appliable',
status: :error)
end
end
diff --git a/spec/services/suggestions/create_service_spec.rb b/spec/services/suggestions/create_service_spec.rb
index 1b4b15b8eaa..ce4990a34a4 100644
--- a/spec/services/suggestions/create_service_spec.rb
+++ b/spec/services/suggestions/create_service_spec.rb
@@ -40,6 +40,14 @@ describe Suggestions::CreateService do
```thing
this is not a suggestion, it's a thing
```
+
+ ```suggestion:-3+2
+ # multi-line suggestion 1
+ ```
+
+ ```suggestion:-5
+ # multi-line suggestion 1
+ ```
MARKDOWN
end
@@ -54,7 +62,7 @@ describe Suggestions::CreateService do
end
it 'does not try to parse suggestions' do
- expect(Banzai::SuggestionsParser).not_to receive(:parse)
+ expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse)
subject.execute
end
@@ -71,7 +79,7 @@ describe Suggestions::CreateService do
it 'does not try to parse suggestions' do
allow(note).to receive(:on_text?) { false }
- expect(Banzai::SuggestionsParser).not_to receive(:parse)
+ expect(Gitlab::Diff::SuggestionsParser).not_to receive(:parse)
subject.execute
end
@@ -87,7 +95,9 @@ describe Suggestions::CreateService do
end
it 'creates no suggestion when diff file is not found' do
- expect(note).to receive(:latest_diff_file) { nil }
+ expect_next_instance_of(DiffNote) do |diff_note|
+ expect(diff_note).to receive(:latest_diff_file).twice { nil }
+ end
expect { subject.execute }.not_to change(Suggestion, :count)
end
@@ -101,43 +111,44 @@ describe Suggestions::CreateService do
note: markdown)
end
- context 'single line suggestions' do
- it 'persists suggestion records' do
- expect { subject.execute }
- .to change { note.suggestions.count }
- .from(0)
- .to(2)
- end
+ let(:expected_suggestions) do
+ Gitlab::Diff::SuggestionsParser.parse(markdown,
+ project: note.project,
+ position: note.position)
+ end
- it 'persists original from_content lines and suggested lines' do
- subject.execute
+ it 'persists suggestion records' do
+ expect { subject.execute }.to change { note.suggestions.count }
+ .from(0).to(expected_suggestions.size)
+ end
- suggestions = note.suggestions.order(:relative_order)
+ it 'persists suggestions data correctly' do
+ subject.execute
- suggestion_1 = suggestions.first
- suggestion_2 = suggestions.last
+ suggestions = note.suggestions.order(:relative_order)
- expect(suggestion_1).to have_attributes(from_content: " vars = {\n",
- to_content: " foo\n bar\n")
+ suggestions.zip(expected_suggestions) do |suggestion, expected_suggestion|
+ expected_data = expected_suggestion.to_hash
- expect(suggestion_2).to have_attributes(from_content: " vars = {\n",
- to_content: " xpto\n baz\n")
+ expect(suggestion.from_content).to eq(expected_data[:from_content])
+ expect(suggestion.to_content).to eq(expected_data[:to_content])
+ expect(suggestion.lines_above).to eq(expected_data[:lines_above])
+ expect(suggestion.lines_below).to eq(expected_data[:lines_below])
end
+ end
- context 'outdated position note' do
- let!(:outdated_diff) { merge_request.merge_request_diff }
- let!(:latest_diff) { merge_request.create_merge_request_diff }
- let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) }
- let(:position) { build_position(diff_refs: latest_diff.diff_refs) }
+ context 'outdated position note' do
+ let!(:outdated_diff) { merge_request.merge_request_diff }
+ let!(:latest_diff) { merge_request.create_merge_request_diff }
+ let(:outdated_position) { build_position(diff_refs: outdated_diff.diff_refs) }
+ let(:position) { build_position(diff_refs: latest_diff.diff_refs) }
- it 'uses the correct position when creating the suggestion' do
- expect(note.position)
- .to receive(:diff_file)
- .with(project_with_repo.repository)
- .and_call_original
+ it 'uses the correct position when creating the suggestion' do
+ expect(Gitlab::Diff::SuggestionsParser).to receive(:parse)
+ .with(note.note, project: note.project, position: note.position)
+ .and_call_original
- subject.execute
- end
+ subject.execute
end
end
end
diff --git a/spec/services/suggestions/outdate_service_spec.rb b/spec/services/suggestions/outdate_service_spec.rb
new file mode 100644
index 00000000000..bcc627013d8
--- /dev/null
+++ b/spec/services/suggestions/outdate_service_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Suggestions::OutdateService do
+ describe '#execute' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.target_project }
+ let(:user) { merge_request.author }
+ let(:file_path) { 'files/ruby/popen.rb' }
+ let(:branch_name) { project.default_branch }
+ let(:diff_file) { suggestion.diff_file }
+ let(:position) { build_position(file_path, comment_line) }
+ let(:note) do
+ create(:diff_note_on_merge_request, noteable: merge_request,
+ position: position,
+ project: project)
+ end
+
+ def build_position(path, line)
+ Gitlab::Diff::Position.new(old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: line,
+ diff_refs: merge_request.diff_refs)
+ end
+
+ def commit_changes(file_path, new_content)
+ params = {
+ file_path: file_path,
+ commit_message: "Update File",
+ file_content: new_content,
+ start_project: project,
+ start_branch: project.default_branch,
+ branch_name: branch_name
+ }
+
+ Files::UpdateService.new(project, user, params).execute
+ end
+
+ def update_file_line(diff_file, change_line, content)
+ new_lines = diff_file.new_blob.data.lines
+ new_lines[change_line..change_line] = content
+ result = commit_changes(diff_file.file_path, new_lines.join)
+ newrev = result[:result]
+
+ expect(result[:status]).to eq(:success)
+ expect(newrev).to be_present
+
+ # Ensure all memoized data is cleared in order
+ # to generate the new merge_request_diff.
+ MergeRequest.find(merge_request.id).reload_diff(user)
+
+ note.reload
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ subject { described_class.new.execute(merge_request) }
+
+ context 'when there is a change within multi-line suggestion range' do
+ let(:comment_line) { 9 }
+ let(:lines_above) { 8 } # suggesting to change lines 1..9
+ let(:change_line) { 2 } # line 2 is within the range
+ let!(:suggestion) do
+ create(:suggestion, :content_from_repo, note: note, lines_above: lines_above)
+ end
+
+ it 'updates the outdatable suggestion record' do
+ update_file_line(diff_file, change_line, "# foo\nbar\n")
+
+ # Make sure note is still active
+ expect(note.active?).to be(true)
+
+ expect { subject }.to change { suggestion.reload.outdated }
+ .from(false).to(true)
+ end
+ end
+
+ context 'when there is no change within multi-line suggestion range' do
+ let(:comment_line) { 9 }
+ let(:lines_above) { 3 } # suggesting to change lines 6..9
+ let(:change_line) { 2 } # line 2 is not within the range
+ let!(:suggestion) do
+ create(:suggestion, :content_from_repo, note: note, lines_above: lines_above)
+ end
+
+ subject { described_class.new.execute(merge_request) }
+
+ it 'does not outdates suggestion record' do
+ update_file_line(diff_file, change_line, "# foo\nbar\n")
+
+ # Make sure note is still active
+ expect(note.active?).to be(true)
+
+ expect { subject }.not_to change { suggestion.reload.outdated }.from(false)
+ end
+ end
+ end
+end
diff --git a/spec/services/tags/create_service_spec.rb b/spec/services/tags/create_service_spec.rb
index 0cbe57352be..e112cdc8881 100644
--- a/spec/services/tags/create_service_spec.rb
+++ b/spec/services/tags/create_service_spec.rb
@@ -41,7 +41,7 @@ describe Tags::CreateService do
it 'returns an error' do
expect(repository).to receive(:add_tag)
.with(user, 'v1.1.0', 'master', 'Foo')
- .and_raise(Gitlab::Git::PreReceiveError, 'something went wrong')
+ .and_raise(Gitlab::Git::PreReceiveError, 'GitLab: something went wrong')
response = service.execute('v1.1.0', 'master', 'Foo')
diff --git a/spec/services/tags/destroy_service_spec.rb b/spec/services/tags/destroy_service_spec.rb
index 7c8c1dd0d3a..a541d300595 100644
--- a/spec/services/tags/destroy_service_spec.rb
+++ b/spec/services/tags/destroy_service_spec.rb
@@ -7,11 +7,27 @@ describe Tags::DestroyService do
let(:service) { described_class.new(project, user) }
describe '#execute' do
+ subject { service.execute(tag_name) }
+
it 'removes the tag' do
expect(repository).to receive(:before_remove_tag)
expect(service).to receive(:success)
service.execute('v1.1.0')
end
+
+ context 'when there is an associated release on the tag' do
+ let(:tag) { repository.tags.first }
+ let(:tag_name) { tag.name }
+
+ before do
+ project.add_maintainer(user)
+ create(:release, tag: tag_name, project: project)
+ end
+
+ it 'destroys the release' do
+ expect { subject }.to change { project.releases.count }.by(-1)
+ end
+ end
end
end
diff --git a/spec/services/verify_pages_domain_service_spec.rb b/spec/services/verify_pages_domain_service_spec.rb
index d974cc0226f..ddf9d2b4917 100644
--- a/spec/services/verify_pages_domain_service_spec.rb
+++ b/spec/services/verify_pages_domain_service_spec.rb
@@ -9,88 +9,130 @@ describe VerifyPagesDomainService do
subject(:service) { described_class.new(domain) }
describe '#execute' do
- context 'verification code recognition (verified domain)' do
- where(:domain_sym, :code_sym) do
- :domain | :verification_code
- :domain | :keyed_verification_code
+ where(:domain_sym, :code_sym) do
+ :domain | :verification_code
+ :domain | :keyed_verification_code
- :verification_domain | :verification_code
- :verification_domain | :keyed_verification_code
- end
-
- with_them do
- set(:domain) { create(:pages_domain) }
+ :verification_domain | :verification_code
+ :verification_domain | :keyed_verification_code
+ end
- let(:domain_name) { domain.send(domain_sym) }
- let(:verification_code) { domain.send(code_sym) }
+ with_them do
+ let(:domain_name) { domain.send(domain_sym) }
+ let(:verification_code) { domain.send(code_sym) }
+ shared_examples 'verifies and enables the domain' do
it 'verifies and enables the domain' do
- stub_resolver(domain_name => ['something else', verification_code])
-
expect(service.execute).to eq(status: :success)
+
expect(domain).to be_verified
expect(domain).to be_enabled
end
+ end
- it 'verifies and enables when the code is contained partway through a TXT record' do
- stub_resolver(domain_name => "something #{verification_code} else")
+ shared_examples 'successful enablement and verification' do
+ context 'when txt record contains verification code' do
+ before do
+ stub_resolver(domain_name => ['something else', verification_code])
+ end
- expect(service.execute).to eq(status: :success)
- expect(domain).to be_verified
- expect(domain).to be_enabled
+ include_examples 'verifies and enables the domain'
end
- it 'does not verify when the code is not present' do
- stub_resolver(domain_name => 'something else')
-
- expect(service.execute).to eq(error_status)
+ context 'when txt record contains verification code with other text' do
+ before do
+ stub_resolver(domain_name => "something #{verification_code} else")
+ end
- expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ include_examples 'verifies and enables the domain'
end
end
- context 'verified domain' do
- set(:domain) { create(:pages_domain) }
+ context 'when domain is disabled(or new)' do
+ let(:domain) { create(:pages_domain, :disabled) }
- it 'unverifies (but does not disable) when the right code is not present' do
- stub_resolver(domain.domain => 'something else')
+ include_examples 'successful enablement and verification'
- expect(service.execute).to eq(error_status)
- expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ shared_examples 'unverifies and disables domain' do
+ it 'unverifies and disables domain' do
+ expect(service.execute).to eq(error_status)
+
+ expect(domain).not_to be_verified
+ expect(domain).not_to be_enabled
+ end
end
- it 'unverifies (but does not disable) when no records are present' do
- stub_resolver
+ context 'when txt record does not contain verification code' do
+ before do
+ stub_resolver(domain_name => 'something else')
+ end
- expect(service.execute).to eq(error_status)
- expect(domain).not_to be_verified
- expect(domain).to be_enabled
+ include_examples 'unverifies and disables domain'
+ end
+
+ context 'when no txt records are present' do
+ before do
+ stub_resolver
+ end
+
+ include_examples 'unverifies and disables domain'
end
end
- context 'expired domain' do
- set(:domain) { create(:pages_domain, :expired) }
+ context 'when domain is verified' do
+ let(:domain) { create(:pages_domain) }
- it 'verifies and enables when the right code is present' do
- stub_resolver(domain.domain => domain.keyed_verification_code)
+ include_examples 'successful enablement and verification'
- expect(service.execute).to eq(status: :success)
+ context 'when txt record does not contain verification code' do
+ before do
+ stub_resolver(domain_name => 'something else')
+ end
- expect(domain).to be_verified
- expect(domain).to be_enabled
+ it 'unverifies but does not disable domain' do
+ expect(service.execute).to eq(error_status)
+ expect(domain).not_to be_verified
+ expect(domain).to be_enabled
+ end
end
- it 'disables when the right code is not present' do
- error_status[:message] += '. It is now disabled.'
+ context 'when no txt records are present' do
+ before do
+ stub_resolver
+ end
- stub_resolver
+ it 'unverifies but does not disable domain' do
+ expect(service.execute).to eq(error_status)
+ expect(domain).not_to be_verified
+ expect(domain).to be_enabled
+ end
+ end
+ end
- expect(service.execute).to eq(error_status)
+ context 'when domain is expired' do
+ let(:domain) { create(:pages_domain, :expired) }
- expect(domain).not_to be_verified
- expect(domain).not_to be_enabled
+ context 'when the right code is present' do
+ before do
+ stub_resolver(domain_name => domain.keyed_verification_code)
+ end
+
+ include_examples 'verifies and enables the domain'
+ end
+
+ context 'when the right code is not present' do
+ before do
+ stub_resolver
+ end
+
+ it 'disables domain' do
+ error_status[:message] += '. It is now disabled.'
+
+ expect(service.execute).to eq(error_status)
+
+ expect(domain).not_to be_verified
+ expect(domain).not_to be_enabled
+ end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index e8d7b18bf04..60db3e1bc46 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -66,6 +66,7 @@ RSpec.configure do |config|
metadata[:type] = match[1].singularize.to_sym if match
end
+ config.include LicenseHelpers
config.include ActiveJob::TestHelper
config.include ActiveSupport::Testing::TimeHelpers
config.include CycleAnalyticsHelpers
@@ -96,6 +97,7 @@ RSpec.configure do |config|
config.include MigrationsHelpers, :migration
config.include RedisHelpers
config.include Rails.application.routes.url_helpers, type: :routing
+ config.include PolicyHelpers, type: :policy
if ENV['CI']
# This includes the first try, i.e. tests will be run 4 times before failing.
diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb
index 5f709831ce1..63b719be03e 100644
--- a/spec/support/api/milestones_shared_examples.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -72,6 +72,15 @@ shared_examples_for 'group and project milestones' do |route_definition|
expect(json_response.first['id']).to eq closed_milestone.id
end
+ it 'returns a milestone by title' do
+ get api(route, user), params: { title: 'version2' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['title']).to eq milestone.title
+ expect(json_response.first['id']).to eq milestone.id
+ end
+
it 'returns a milestone by searching for title' do
get api(route, user), params: { search: 'version2' }
diff --git a/spec/support/features/issuable_quick_actions_shared_examples.rb b/spec/support/features/issuable_quick_actions_shared_examples.rb
deleted file mode 100644
index 2a883ce1074..00000000000
--- a/spec/support/features/issuable_quick_actions_shared_examples.rb
+++ /dev/null
@@ -1,389 +0,0 @@
-# Specifications for behavior common to all objects with executable attributes.
-# It takes a `issuable_type`, and expect an `issuable`.
-
-shared_examples 'issuable record that supports quick actions in its description and notes' do |issuable_type|
- include Spec::Support::Helpers::Features::NotesHelpers
-
- let(:maintainer) { create(:user) }
- let(:project) do
- case issuable_type
- when :merge_request
- create(:project, :public, :repository)
- when :issue
- create(:project, :public)
- end
- end
- let!(:milestone) { create(:milestone, project: project, title: 'ASAP') }
- let!(:label_bug) { create(:label, project: project, title: 'bug') }
- let!(:label_feature) { create(:label, project: project, title: 'feature') }
- let(:new_url_opts) { {} }
-
- before do
- project.add_maintainer(maintainer)
-
- gitlab_sign_in(maintainer)
- end
-
- after do
- # Ensure all outstanding Ajax requests are complete to avoid database deadlocks
- wait_for_requests
- end
-
- describe "new #{issuable_type}", :js do
- context 'with commands in the description' do
- it "creates the #{issuable_type} and interpret commands accordingly" do
- case issuable_type
- when :merge_request
- visit public_send("namespace_project_new_merge_request_path", project.namespace, project, new_url_opts)
- when :issue
- visit public_send("new_namespace_project_issue_path", project.namespace, project, new_url_opts)
- end
- fill_in "#{issuable_type}_title", with: 'bug 345'
- fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug\n/milestone %\"ASAP\""
- click_button "Submit #{issuable_type}".humanize
-
- issuable = project.public_send(issuable_type.to_s.pluralize).first
-
- expect(issuable.description).to eq "bug description"
- expect(issuable.labels).to eq [label_bug]
- expect(issuable.milestone).to eq milestone
- expect(page).to have_content 'bug 345'
- expect(page).to have_content 'bug description'
- end
- end
- end
-
- describe "note on #{issuable_type}", :js do
- before do
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- context 'with a note containing commands' do
- it 'creates a note without the commands and interpret the commands accordingly' do
- assignee = create(:user, username: 'bob')
- add_note("Awesome!\n\n/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
-
- expect(page).to have_content 'Awesome!'
- expect(page).not_to have_content '/assign @bob'
- expect(page).not_to have_content '/label ~bug'
- expect(page).not_to have_content '/milestone %"ASAP"'
-
- wait_for_requests
- issuable.reload
- note = issuable.notes.user.first
-
- expect(note.note).to eq "Awesome!"
- expect(issuable.assignees).to eq [assignee]
- expect(issuable.labels).to eq [label_bug]
- expect(issuable.milestone).to eq milestone
- end
-
- it 'removes the quick action from note and explains it in the preview' do
- preview_note("Awesome!\n\n/close")
-
- expect(page).to have_content 'Awesome!'
- expect(page).not_to have_content '/close'
- issuable_name = issuable.is_a?(Issue) ? 'issue' : 'merge request'
- expect(page).to have_content "Closes this #{issuable_name}."
- end
- end
-
- context 'with a note containing only commands' do
- it 'does not create a note but interpret the commands accordingly' do
- assignee = create(:user, username: 'bob')
- add_note("/assign @bob\n\n/label ~bug\n\n/milestone %\"ASAP\"")
-
- expect(page).not_to have_content '/assign @bob'
- expect(page).not_to have_content '/label ~bug'
- expect(page).not_to have_content '/milestone %"ASAP"'
- expect(page).to have_content 'Commands applied'
-
- issuable.reload
-
- expect(issuable.notes.user).to be_empty
- expect(issuable.assignees).to eq [assignee]
- expect(issuable.labels).to eq [label_bug]
- expect(issuable.milestone).to eq milestone
- end
- end
-
- context "with a note closing the #{issuable_type}" do
- before do
- expect(issuable).to be_open
- end
-
- context "when current user can close #{issuable_type}" do
- it "closes the #{issuable_type}" do
- add_note("/close")
-
- expect(page).not_to have_content '/close'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload).to be_closed
- end
- end
-
- context "when current user cannot close #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not close the #{issuable_type}" do
- add_note("/close")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable).to be_open
- end
- end
- end
-
- context "with a note reopening the #{issuable_type}" do
- before do
- issuable.close
- expect(issuable).to be_closed
- end
-
- context "when current user can reopen #{issuable_type}" do
- it "reopens the #{issuable_type}" do
- add_note("/reopen")
-
- expect(page).not_to have_content '/reopen'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload).to be_open
- end
- end
-
- context "when current user cannot reopen #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not reopen the #{issuable_type}" do
- add_note("/reopen")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable).to be_closed
- end
- end
- end
-
- context "with a note changing the #{issuable_type}'s title" do
- context "when current user can change title of #{issuable_type}" do
- it "reopens the #{issuable_type}" do
- add_note("/title Awesome new title")
-
- expect(page).not_to have_content '/title'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload.title).to eq 'Awesome new title'
- end
- end
-
- context "when current user cannot change title of #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- gitlab_sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not change the #{issuable_type} title" do
- add_note("/title Awesome new title")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable.reload.title).not_to eq 'Awesome new title'
- end
- end
- end
-
- context "with a note marking the #{issuable_type} as todo" do
- it "creates a new todo for the #{issuable_type}" do
- add_note("/todo")
-
- expect(page).not_to have_content '/todo'
- expect(page).to have_content 'Commands applied'
-
- todos = TodosFinder.new(maintainer).execute
- todo = todos.first
-
- expect(todos.size).to eq 1
- expect(todo).to be_pending
- expect(todo.target).to eq issuable
- expect(todo.author).to eq maintainer
- expect(todo.user).to eq maintainer
- end
- end
-
- context "with a note marking the #{issuable_type} as done" do
- before do
- TodoService.new.mark_todo(issuable, maintainer)
- end
-
- it "creates a new todo for the #{issuable_type}" do
- todos = TodosFinder.new(maintainer).execute
- todo = todos.first
-
- expect(todos.size).to eq 1
- expect(todos.first).to be_pending
- expect(todo.target).to eq issuable
- expect(todo.author).to eq maintainer
- expect(todo.user).to eq maintainer
-
- add_note("/done")
-
- expect(page).not_to have_content '/done'
- expect(page).to have_content 'Commands applied'
-
- expect(todo.reload).to be_done
- end
- end
-
- context "with a note subscribing to the #{issuable_type}" do
- it "creates a new todo for the #{issuable_type}" do
- expect(issuable.subscribed?(maintainer, project)).to be_falsy
-
- add_note("/subscribe")
-
- expect(page).not_to have_content '/subscribe'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.subscribed?(maintainer, project)).to be_truthy
- end
- end
-
- context "with a note unsubscribing to the #{issuable_type} as done" do
- before do
- issuable.subscribe(maintainer, project)
- end
-
- it "creates a new todo for the #{issuable_type}" do
- expect(issuable.subscribed?(maintainer, project)).to be_truthy
-
- add_note("/unsubscribe")
-
- expect(page).not_to have_content '/unsubscribe'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.subscribed?(maintainer, project)).to be_falsy
- end
- end
-
- context "with a note assigning the #{issuable_type} to the current user" do
- it "assigns the #{issuable_type} to the current user" do
- add_note("/assign me")
-
- expect(page).not_to have_content '/assign me'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload.assignees).to eq [maintainer]
- end
- end
-
- context "with a note locking the #{issuable_type} discussion" do
- before do
- issuable.update(discussion_locked: false)
- expect(issuable).not_to be_discussion_locked
- end
-
- context "when current user can lock #{issuable_type} discussion" do
- it "locks the #{issuable_type} discussion" do
- add_note("/lock")
-
- expect(page).not_to have_content '/lock'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload).to be_discussion_locked
- end
- end
-
- context "when current user cannot lock #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not lock the #{issuable_type} discussion" do
- add_note("/lock")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable).not_to be_discussion_locked
- end
- end
- end
-
- context "with a note unlocking the #{issuable_type} discussion" do
- before do
- issuable.update(discussion_locked: true)
- expect(issuable).to be_discussion_locked
- end
-
- context "when current user can unlock #{issuable_type} discussion" do
- it "unlocks the #{issuable_type} discussion" do
- add_note("/unlock")
-
- expect(page).not_to have_content '/unlock'
- expect(page).to have_content 'Commands applied'
-
- expect(issuable.reload).not_to be_discussion_locked
- end
- end
-
- context "when current user cannot unlock #{issuable_type}" do
- before do
- guest = create(:user)
- project.add_guest(guest)
-
- gitlab_sign_out
- sign_in(guest)
- visit public_send("project_#{issuable_type}_path", project, issuable)
- end
-
- it "does not unlock the #{issuable_type} discussion" do
- add_note("/unlock")
-
- expect(page).not_to have_content 'Commands applied'
-
- expect(issuable).to be_discussion_locked
- end
- end
- end
- end
-
- describe "preview of note on #{issuable_type}", :js do
- it 'removes quick actions from note and explains them' do
- create(:user, username: 'bob')
-
- visit public_send("project_#{issuable_type}_path", project, issuable)
-
- page.within('.js-main-target-form') do
- fill_in 'note[note]', with: "Awesome!\n/assign @bob "
- click_on 'Preview'
-
- expect(page).to have_content 'Awesome!'
- expect(page).not_to have_content '/assign @bob'
- expect(page).to have_content 'Assigns @bob.'
- end
- end
- end
-end
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index ecefdc23811..33648292037 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -23,7 +23,7 @@ module CycleAnalyticsHelpers
return if skip_push_handler
- GitPushService.new(project,
+ Git::BranchPushService.new(project,
user,
oldrev: oldrev,
newrev: commit_shas.last,
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index cceb179d53e..9cae8f934db 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -24,7 +24,7 @@ module JavaScriptFixturesHelpers
#
def clean_frontend_fixtures(directory_name)
full_directory_name = File.expand_path(directory_name, fixture_root_path)
- Dir[File.expand_path('*.html.raw', full_directory_name)].each do |file_name|
+ Dir[File.expand_path('*.html', full_directory_name)].each do |file_name|
FileUtils.rm(file_name)
end
end
diff --git a/spec/support/helpers/license_helper.rb b/spec/support/helpers/license_helper.rb
new file mode 100644
index 00000000000..4aaad55a8ef
--- /dev/null
+++ b/spec/support/helpers/license_helper.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# Placeholder module for EE implementation needed for CE specs to be run in EE codebase
+module LicenseHelpers
+ def stub_licensed_features(features)
+ # do nothing
+ end
+end
diff --git a/spec/support/helpers/policy_helpers.rb b/spec/support/helpers/policy_helpers.rb
new file mode 100644
index 00000000000..3d780eb5fb1
--- /dev/null
+++ b/spec/support/helpers/policy_helpers.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module PolicyHelpers
+ def expect_allowed(*permissions)
+ permissions.each { |p| is_expected.to be_allowed(p) }
+ end
+
+ def expect_disallowed(*permissions)
+ permissions.each { |p| is_expected.not_to be_allowed(p) }
+ end
+end
diff --git a/spec/support/helpers/stub_worker.rb b/spec/support/helpers/stub_worker.rb
new file mode 100644
index 00000000000..58b7ee93dff
--- /dev/null
+++ b/spec/support/helpers/stub_worker.rb
@@ -0,0 +1,9 @@
+# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
+module StubWorker
+ def stub_worker(queue:)
+ Class.new do
+ include Sidekiq::Worker
+ sidekiq_options queue: queue
+ end
+ end
+end
diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb
index f5d9a97051a..62f510b0fbd 100644
--- a/spec/support/matchers/issuable_matchers.rb
+++ b/spec/support/matchers/issuable_matchers.rb
@@ -1,4 +1,4 @@
-RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".wiki"|
+RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"|
match do |actual|
node = find("#{parent} h#{level} a#user-content-#{id}")
diff --git a/spec/support/shared_context/policies/project_policy_shared_context.rb b/spec/support/shared_context/policies/project_policy_shared_context.rb
new file mode 100644
index 00000000000..8bcd26ec0cd
--- /dev/null
+++ b/spec/support/shared_context/policies/project_policy_shared_context.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'ProjectPolicy context' do
+ set(:guest) { create(:user) }
+ set(:reporter) { create(:user) }
+ set(:developer) { create(:user) }
+ set(:maintainer) { create(:user) }
+ set(:owner) { create(:user) }
+ set(:admin) { create(:admin) }
+ let(:project) { create(:project, :public, namespace: owner.namespace) }
+
+ let(:base_guest_permissions) do
+ %i[
+ read_project read_board read_list read_wiki read_issue
+ read_project_for_iids read_issue_iid read_label
+ read_milestone read_project_snippet read_project_member read_note
+ create_project create_issue create_note upload_file create_merge_request_in
+ award_emoji read_release
+ ]
+ end
+
+ let(:base_reporter_permissions) do
+ %i[
+ download_code fork_project create_project_snippet update_issue
+ admin_issue admin_label admin_list read_commit_status read_build
+ read_container_image read_pipeline read_environment read_deployment
+ read_merge_request download_wiki_code read_sentry_issue
+ ]
+ end
+
+ let(:team_member_reporter_permissions) do
+ %i[build_download_code build_read_container_image]
+ end
+
+ let(:developer_permissions) do
+ %i[
+ admin_milestone admin_merge_request update_merge_request create_commit_status
+ update_commit_status create_build update_build create_pipeline
+ update_pipeline create_merge_request_from create_wiki push_code
+ resolve_note create_container_image update_container_image
+ create_environment create_deployment create_release update_release
+ ]
+ end
+
+ let(:base_maintainer_permissions) do
+ %i[
+ push_to_delete_protected_branch update_project_snippet update_environment
+ update_deployment admin_project_snippet admin_project_member admin_note admin_wiki admin_project
+ admin_commit_status admin_build admin_container_image
+ admin_pipeline admin_environment admin_deployment destroy_release add_cluster
+ daily_statistics
+ ]
+ end
+
+ let(:public_permissions) do
+ %i[
+ download_code fork_project read_commit_status read_pipeline
+ read_container_image build_download_code build_read_container_image
+ download_wiki_code read_release
+ ]
+ end
+
+ let(:base_owner_permissions) do
+ %i[
+ change_namespace change_visibility_level rename_project remove_project
+ archive_project remove_fork_project destroy_merge_request destroy_issue
+ set_issue_iid set_issue_created_at set_note_created_at
+ ]
+ end
+
+ # Used in EE specs
+ let(:additional_guest_permissions) { [] }
+ let(:additional_reporter_permissions) { [] }
+ let(:additional_maintainer_permissions) { [] }
+ let(:additional_owner_permissions) { [] }
+
+ let(:guest_permissions) { base_guest_permissions + additional_guest_permissions }
+ let(:reporter_permissions) { base_reporter_permissions + additional_reporter_permissions }
+ let(:maintainer_permissions) { base_maintainer_permissions + additional_maintainer_permissions }
+ let(:owner_permissions) { base_owner_permissions + additional_owner_permissions }
+
+ before do
+ project.add_guest(guest)
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ end
+end
diff --git a/spec/support/shared_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
new file mode 100644
index 00000000000..b4808ac0068
--- /dev/null
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'GroupPolicy context' do
+ let(:guest) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:maintainer) { create(:user) }
+ let(:owner) { create(:user) }
+ let(:admin) { create(:admin) }
+ let(:group) { create(:group, :private) }
+
+ let(:guest_permissions) do
+ %i[
+ read_label read_group upload_file read_namespace read_group_activity
+ read_group_issues read_group_boards read_group_labels read_group_milestones
+ read_group_merge_requests
+ ]
+ end
+ let(:reporter_permissions) { [:admin_label] }
+ let(:developer_permissions) { [:admin_milestone] }
+ let(:maintainer_permissions) do
+ %i[
+ create_projects
+ read_cluster create_cluster update_cluster admin_cluster add_cluster
+ ]
+ end
+ let(:owner_permissions) do
+ [
+ :admin_group,
+ :admin_namespace,
+ :admin_group_member,
+ :change_visibility_level,
+ :set_note_created_at,
+ (Gitlab::Database.postgresql? ? :create_subgroup : nil)
+ ].compact
+ end
+
+ before do
+ group.add_guest(guest)
+ group.add_reporter(reporter)
+ group.add_developer(developer)
+ group.add_maintainer(maintainer)
+ group.add_owner(owner)
+ end
+
+ subject { described_class.new(current_user, group) }
+end
diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
new file mode 100644
index 00000000000..7a71e2ee370
--- /dev/null
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -0,0 +1,231 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'archived project policies' do
+ let(:feature_write_abilities) do
+ described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature|
+ described_class.create_update_admin_destroy(feature)
+ end + additional_reporter_permissions + additional_maintainer_permissions
+ end
+
+ let(:other_write_abilities) do
+ %i[
+ create_merge_request_in
+ create_merge_request_from
+ push_to_delete_protected_branch
+ push_code
+ request_access
+ upload_file
+ resolve_note
+ award_emoji
+ ]
+ end
+
+ context 'when the project is archived' do
+ before do
+ project.archived = true
+ end
+
+ it 'disables write actions on all relevant project features' do
+ expect_disallowed(*feature_write_abilities)
+ end
+
+ it 'disables some other important write actions' do
+ expect_disallowed(*other_write_abilities)
+ end
+
+ it 'does not disable other abilities' do
+ expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities))
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as anonymous' do
+ context 'abilities for public projects' do
+ context 'when a project has pending invites' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, namespace: group) }
+ let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
+ let(:anonymous_permissions) { guest_permissions - user_permissions }
+
+ subject { described_class.new(nil, project) }
+
+ before do
+ create(:group_member, :invited, group: group)
+ end
+
+ it 'does not grant owner access' do
+ expect_allowed(*anonymous_permissions)
+ expect_disallowed(*user_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { anonymous_permissions }
+ end
+ end
+ end
+
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(nil, project) }
+
+ it { is_expected.to be_banned }
+ end
+end
+
+RSpec.shared_examples 'project policies as guest' do
+ subject { described_class.new(guest, project) }
+
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+ let(:reporter_public_build_permissions) do
+ reporter_permissions - [:read_build, :read_pipeline]
+ end
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_disallowed(*reporter_public_build_permissions)
+ expect_disallowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { guest_permissions }
+ end
+
+ context 'public builds enabled' do
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(:read_build, :read_pipeline)
+ end
+ end
+
+ context 'when public builds disabled' do
+ before do
+ project.update(public_builds: false)
+ end
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_disallowed(:read_build, :read_pipeline)
+ end
+ end
+
+ context 'when builds are disabled' do
+ before do
+ project.project_feature.update(builds_access_level: ProjectFeature::DISABLED)
+ end
+
+ it do
+ expect_disallowed(:read_build)
+ expect_allowed(:read_pipeline)
+ end
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as reporter' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(reporter, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { reporter_permissions }
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as developer' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+ subject { described_class.new(developer, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { developer_permissions }
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as maintainer' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(maintainer, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { maintainer_permissions }
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as owner' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(owner, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_allowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { owner_permissions }
+ end
+ end
+end
+
+RSpec.shared_examples 'project policies as admin' do
+ context 'abilities for non-public projects' do
+ let(:project) { create(:project, namespace: owner.namespace) }
+
+ subject { described_class.new(admin, project) }
+
+ it do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_disallowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_allowed(*owner_permissions)
+ end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { owner_permissions }
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..4604d867507
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'tag quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..d97da6be192
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/assign_quick_action_shared_examples.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+shared_examples 'assign quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets assign quick action accordingly" do
+ assignee = create(:user, username: 'bob')
+
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/assign @bob"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.assignees).to eq [assignee]
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+
+ it "creates the #{issuable_type} and interprets assign quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/assign me"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.assignees).to eq [maintainer]
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the assign quick action accordingly' do
+ assignee = create(:user, username: 'bob')
+ add_note("Awesome!\n\n/assign @bob")
+
+ expect(page).to have_content 'Awesome!'
+ expect(page).not_to have_content '/assign @bob'
+
+ wait_for_requests
+ issuable.reload
+ note = issuable.notes.user.first
+
+ expect(note.note).to eq 'Awesome!'
+ expect(issuable.assignees).to eq [assignee]
+ end
+
+ it "assigns the #{issuable_type} to the current user" do
+ add_note("/assign me")
+
+ expect(page).not_to have_content '/assign me'
+ expect(page).to have_content 'Commands applied'
+
+ expect(issuable.reload.assignees).to eq [maintainer]
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains assign quick action to bob' do
+ create(:user, username: 'bob')
+
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "Awesome!\n/assign @bob "
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/assign @bob'
+ expect(page).to have_content 'Awesome!'
+ expect(page).to have_content 'Assigns @bob.'
+ end
+ end
+
+ it 'explains assign quick action to me' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "Awesome!\n/assign me"
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/assign me'
+ expect(page).to have_content 'Awesome!'
+ expect(page).to have_content "Assigns @#{maintainer.username}."
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..74cbfa3f4b4
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/award_quick_action_shared_examples.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+shared_examples 'award quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets award quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/award :100:"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.award_emoji).to eq []
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ expect(issuable.award_emoji).to eq []
+ end
+
+ it 'creates the note and interprets the award quick action accordingly' do
+ add_note("/award :100:")
+
+ wait_for_requests
+ expect(page).not_to have_content '/award'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.award_emoji.last.name).to eq('100')
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains label quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/award :100:')
+
+ expect(page).not_to have_content '/award'
+ expect(page).to have_selector "gl-emoji[data-name='100']"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..e0d0b790a0e
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+shared_examples 'close quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets close quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/close"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ expect(issuable).to be_opened
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the close quick action accordingly' do
+ add_note("this is done, close\n\n/close")
+
+ wait_for_requests
+ expect(page).not_to have_content '/close'
+ expect(page).to have_content 'this is done, close'
+
+ issuable.reload
+ note = issuable.notes.user.first
+
+ expect(note.note).to eq 'this is done, close'
+ expect(issuable).to be_closed
+ end
+
+ context "when current user cannot close #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ end
+
+ it "does not close the #{issuable_type}" do
+ add_note('/close')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(issuable).to be_open
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains close quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "this is done, close\n/close"
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/close'
+ expect(page).to have_content 'this is done, close'
+ expect(page).to have_content "Closes this #{issuable_type.to_s.humanize.downcase}."
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..1e1e3c7bc95
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/copy_metadata_quick_action_shared_examples.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+shared_examples 'copy_metadata quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets copy_metadata quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/copy_metadata #{source_issuable.to_reference(project)}"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).last
+
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ issuable.reload
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.milestone).to eq milestone
+ expect(issuable.labels).to match_array([label_bug, label_feature])
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets copy_metadata quick action accordingly' do
+ add_note("/copy_metadata #{source_issuable.to_reference(project)}")
+
+ wait_for_requests
+ expect(page).not_to have_content '/copy_metadata'
+ expect(page).to have_content 'Commands applied'
+ issuable.reload
+ expect(issuable.milestone).to eq milestone
+ expect(issuable.labels).to match_array([label_bug, label_feature])
+ end
+
+ context "when current user cannot copy_metadata" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not copy_metadata' do
+ add_note("/copy_metadata #{source_issuable.to_reference(project)}")
+
+ wait_for_requests
+ expect(page).not_to have_content '/copy_metadata'
+ expect(page).not_to have_content 'Commands applied'
+ issuable.reload
+ expect(issuable.milestone).not_to eq milestone
+ expect(issuable.labels).to eq []
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains copy_metadata quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note("/copy_metadata #{source_issuable.to_reference(project)}")
+
+ expect(page).not_to have_content '/copy_metadata'
+ expect(page).to have_content "Copy labels and milestone from #{source_issuable.to_reference(project)}."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..8a72bbc13bf
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/done_quick_action_shared_examples.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+shared_examples 'done quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets done quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/done"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+
+ todos = TodosFinder.new(maintainer).execute
+ expect(todos.size).to eq 0
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ TodoService.new.mark_todo(issuable, maintainer)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the done quick action accordingly' do
+ todos = TodosFinder.new(maintainer).execute
+ todo = todos.first
+ expect(todo.reload).to be_pending
+
+ expect(todos.size).to eq 1
+ expect(todo.target).to eq issuable
+ expect(todo.author).to eq maintainer
+ expect(todo.user).to eq maintainer
+
+ add_note('/done')
+
+ wait_for_requests
+ expect(page).not_to have_content '/done'
+ expect(page).to have_content 'Commands applied'
+ expect(todo.reload).to be_done
+ end
+
+ context "when current user cannot mark #{issuable_type} todo as done" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not set the #{issuable_type} todo as done" do
+ todos = TodosFinder.new(maintainer).execute
+ todo = todos.first
+ expect(todo.reload).to be_pending
+
+ expect(todos.size).to eq 1
+ expect(todo.target).to eq issuable
+ expect(todo.author).to eq maintainer
+ expect(todo.user).to eq maintainer
+
+ add_note('/done')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(todo.reload).to be_pending
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains done quick action' do
+ TodoService.new.mark_todo(issuable, maintainer)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/done')
+
+ expect(page).not_to have_content '/done'
+ expect(page).to have_content "Marks todo as done."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..648755d7e55
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/estimate_quick_action_shared_examples.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+shared_examples 'estimate quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets estimate quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/estimate 1d 2h 3m"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.time_estimate).to eq 36180
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the estimate quick action accordingly' do
+ add_note("/estimate 1d 2h 3m")
+
+ wait_for_requests
+ expect(page).not_to have_content '/estimate'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.time_estimate).to eq 36180
+ end
+
+ context "when current user cannot set estimate to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set estimate' do
+ add_note("/estimate ~bug ~feature")
+
+ wait_for_requests
+ expect(page).not_to have_content '/estimate'
+ expect(issuable.reload.time_estimate).to eq 0
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains estimate quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/estimate 1d 2h 3m')
+
+ expect(page).not_to have_content '/estimate'
+ expect(page).to have_content 'Sets time estimate to 1d 2h 3m.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..9066e382b70
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/label_quick_action_shared_examples.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+shared_examples 'label quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets label quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug ~feature"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.labels).to match_array([label_bug, label_feature])
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ expect(issuable.labels).to eq []
+ end
+
+ it 'creates the note and interprets the label quick action accordingly' do
+ add_note("/label ~bug ~feature")
+
+ wait_for_requests
+ expect(page).not_to have_content '/label'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to match_array([label_bug, label_feature])
+ end
+
+ context "when current user cannot set label to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set label' do
+ add_note("/label ~bug ~feature")
+
+ wait_for_requests
+ expect(page).not_to have_content '/label'
+ expect(issuable.labels).to eq []
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains label quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/label ~bug ~feature')
+
+ expect(page).not_to have_content '/label'
+ expect(page).to have_content 'Adds bug feature labels.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..d3197f2a459
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/lock_quick_action_shared_examples.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+shared_examples 'lock quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets lock quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/lock"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable).not_to be_discussion_locked
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ issuable.update(discussion_locked: false)
+ expect(issuable).not_to be_discussion_locked
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the lock quick action accordingly' do
+ add_note('/lock')
+
+ wait_for_requests
+ expect(page).not_to have_content '/lock'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload).to be_discussion_locked
+ end
+
+ context "when current user cannot lock to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not lock the #{issuable_type}" do
+ add_note('/lock')
+
+ wait_for_requests
+ expect(page).not_to have_content '/lock'
+ expect(issuable).not_to be_discussion_locked
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains lock quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/lock')
+
+ expect(page).not_to have_content '/lock'
+ expect(page).to have_content "Locks the discussion"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..7f16ce93b6a
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/milestone_quick_action_shared_examples.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+shared_examples 'milestone quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets milestone quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/milestone %\"ASAP\""
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.milestone).to eq milestone
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ expect(issuable.milestone).to be_nil
+ end
+
+ it 'creates the note and interprets the milestone quick action accordingly' do
+ add_note("/milestone %\"ASAP\"")
+
+ wait_for_requests
+ expect(page).not_to have_content '/milestone'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.milestone).to eq milestone
+ end
+
+ context "when current user cannot set milestone to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set milestone' do
+ add_note('/milestone')
+
+ wait_for_requests
+ expect(page).not_to have_content '/milestone'
+ expect(issuable.reload.milestone).to be_nil
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains milestone quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note("/milestone %\"ASAP\"")
+
+ expect(page).not_to have_content '/milestone'
+ expect(page).to have_content 'Sets the milestone to %ASAP'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..643ae77516a
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/relabel_quick_action_shared_examples.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+shared_examples 'relabel quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets relabel quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /relabel ~feature"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.labels).to eq [label_bug, label_feature]
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.update(labels: [label_bug])
+ end
+
+ it 'creates the note and interprets the relabel quick action accordingly' do
+ add_note('/relabel ~feature')
+
+ wait_for_requests
+ expect(page).not_to have_content '/relabel'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to match_array([label_feature])
+ end
+
+ it 'creates the note and interprets the relabel quick action with empty param' do
+ add_note('/relabel')
+
+ wait_for_requests
+ expect(page).not_to have_content '/relabel'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to match_array([label_bug])
+ end
+
+ context "when current user cannot relabel to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not relabel' do
+ add_note('/relabel ~feature')
+
+ wait_for_requests
+ expect(page).not_to have_content '/relabel'
+ expect(issuable.labels).to match_array([label_bug])
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ before do
+ issuable.update(labels: [label_bug])
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ end
+
+ it 'explains relabel all quick action' do
+ preview_note('/relabel ~feature')
+
+ expect(page).not_to have_content '/relabel'
+ expect(page).to have_content 'Replaces all labels with feature label.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..24f6f8d5bf4
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/remove_estimate_quick_action_shared_examples.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+shared_examples 'remove_estimate quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets estimate quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/remove_estimate"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.time_estimate).to eq 0
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.update_attribute(:time_estimate, 36180)
+ end
+
+ it 'creates the note and interprets the remove_estimate quick action accordingly' do
+ add_note("/remove_estimate")
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_estimate'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.time_estimate).to eq 0
+ end
+
+ context "when current user cannot remove_estimate" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not remove_estimate' do
+ add_note('/remove_estimate')
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_estimate'
+ expect(issuable.reload.time_estimate).to eq 36180
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains remove_estimate quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/remove_estimate')
+
+ expect(page).not_to have_content '/remove_estimate'
+ expect(page).to have_content 'Removes time estimate.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..edd92d5cdbc
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/remove_milestone_quick_action_shared_examples.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+shared_examples 'remove_milestone quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets remove_milestone quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/remove_milestone"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.milestone).to be_nil
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.update(milestone: milestone)
+ expect(issuable.milestone).to eq(milestone)
+ end
+
+ it 'creates the note and interprets the remove_milestone quick action accordingly' do
+ add_note("/remove_milestone")
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_milestone'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.milestone).to be_nil
+ end
+
+ context "when current user cannot remove milestone to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not remove milestone' do
+ add_note('/remove_milestone')
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_milestone'
+ expect(issuable.reload.milestone).to eq(milestone)
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains remove_milestone quick action' do
+ issuable.update(milestone: milestone)
+ expect(issuable.milestone).to eq(milestone)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note("/remove_milestone")
+
+ expect(page).not_to have_content '/remove_milestone'
+ expect(page).to have_content 'Removes %ASAP milestone.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..6d5894b2318
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/remove_time_spent_quick_action_shared_examples.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+shared_examples 'remove_time_spent quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets remove_time_spent quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/remove_time_spent"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.total_time_spent).to eq 0
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ issuable.update!(spend_time: { duration: 36180, user_id: maintainer.id })
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the remove_time_spent quick action accordingly' do
+ add_note("/remove_time_spent")
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_time_spent'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.total_time_spent).to eq 0
+ end
+
+ context "when current user cannot set remove_time_spent time" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set remove_time_spent time' do
+ add_note("/remove_time_spent")
+
+ wait_for_requests
+ expect(page).not_to have_content '/remove_time_spent'
+ expect(issuable.reload.total_time_spent).to eq 36180
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains remove_time_spent quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/remove_time_spent')
+
+ expect(page).not_to have_content '/remove_time_spent'
+ expect(page).to have_content 'Removes spent time.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..af173e93bb5
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/reopen_quick_action_shared_examples.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+shared_examples 'reopen quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets reopen quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/reopen"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ issuable.close
+ expect(issuable).to be_closed
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the reopen quick action accordingly' do
+ add_note('/reopen')
+
+ wait_for_requests
+ expect(page).not_to have_content '/reopen'
+ expect(page).to have_content 'Commands applied'
+
+ issuable.reload
+ expect(issuable).to be_opened
+ end
+
+ context "when current user cannot reopen #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not reopen the #{issuable_type}" do
+ add_note('/reopen')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(issuable).to be_closed
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains reopen quick action' do
+ issuable.close
+ expect(issuable).to be_closed
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/reopen')
+
+ expect(page).not_to have_content '/reopen'
+ expect(page).to have_content "Reopens this #{issuable_type.to_s.humanize.downcase}."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..0a526808585
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/shrug_quick_action_shared_examples.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+shared_examples 'shrug quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets shrug quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/shrug oops"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq "bug description\noops ¯\\_(ツ)_/¯"
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content "bug description\noops ¯\\_(ツ)_/¯"
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets shrug quick action accordingly' do
+ add_note("/shrug oops")
+
+ wait_for_requests
+ expect(page).not_to have_content '/shrug oops'
+ expect(page).to have_content "oops ¯\\_(ツ)_/¯"
+ expect(issuable.notes.last.note).to eq "oops ¯\\_(ツ)_/¯"
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains shrug quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/shrug oops')
+
+ expect(page).not_to have_content '/shrug'
+ expect(page).to have_content "oops ¯\\_(ツ)_/¯"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..97b4885eba0
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/spend_quick_action_shared_examples.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+shared_examples 'spend quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets spend quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/spend 1d 2h 3m"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.total_time_spent).to eq 36180
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the spend quick action accordingly' do
+ add_note("/spend 1d 2h 3m")
+
+ wait_for_requests
+ expect(page).not_to have_content '/spend'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.total_time_spent).to eq 36180
+ end
+
+ context "when current user cannot set spend time" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not set spend time' do
+ add_note("/spend 1s 2h 3m")
+
+ wait_for_requests
+ expect(page).not_to have_content '/spend'
+ expect(issuable.reload.total_time_spent).to eq 0
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains spend quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/spend 1d 2h 3m')
+
+ expect(page).not_to have_content '/spend'
+ expect(page).to have_content 'Adds 1d 2h 3m spent time.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..15aefd511a5
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/subscribe_quick_action_shared_examples.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+shared_examples 'subscribe quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets subscribe quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/subscribe"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ expect(issuable.subscribed?(maintainer, project)).to be_falsy
+ end
+
+ it 'creates the note and interprets the subscribe quick action accordingly' do
+ add_note('/subscribe')
+
+ wait_for_requests
+ expect(page).not_to have_content '/subscribe'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+
+ context "when current user cannot subscribe to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not subscribe to the #{issuable_type}" do
+ add_note('/subscribe')
+
+ wait_for_requests
+ expect(page).not_to have_content '/subscribe'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.subscribed?(maintainer, project)).to be_falsy
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains subscribe quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/subscribe')
+
+ expect(page).not_to have_content '/subscribe'
+ expect(page).to have_content "Subscribes to this #{issuable_type.to_s.humanize.downcase}"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..ef831e39872
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/tableflip_quick_action_shared_examples.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+shared_examples 'tableflip quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets tableflip quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/tableflip oops"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq "bug description\noops (╯°□°)╯︵ ┻━┻"
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content "bug description\noops (╯°□°)╯︵ ┻━┻"
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets tableflip quick action accordingly' do
+ add_note("/tableflip oops")
+
+ wait_for_requests
+ expect(page).not_to have_content '/tableflip oops'
+ expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻"
+ expect(issuable.notes.last.note).to eq "oops (╯°□°)╯︵ ┻━┻"
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains tableflip quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/tableflip oops')
+
+ expect(page).not_to have_content '/tableflip'
+ expect(page).to have_content "oops (╯°□°)╯︵ ┻━┻"
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..ed904c8d539
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+shared_examples 'issuable time tracker' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ after do
+ wait_for_requests
+ end
+
+ it 'renders the sidebar component empty state' do
+ page.within '.time-tracking-no-tracking-pane' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'updates the sidebar component when estimate is added' do
+ submit_time('/estimate 3w 1d 1h')
+
+ wait_for_requests
+ page.within '.time-tracking-estimate-only-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'updates the sidebar component when spent is added' do
+ submit_time('/spend 3w 1d 1h')
+
+ wait_for_requests
+ page.within '.time-tracking-spend-only-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'shows the comparison when estimate and spent are added' do
+ submit_time('/estimate 3w 1d 1h')
+ submit_time('/spend 3w 1d 1h')
+
+ wait_for_requests
+ page.within '.time-tracking-comparison-pane' do
+ expect(page).to have_content '3w 1d 1h'
+ end
+ end
+
+ it 'updates the sidebar component when estimate is removed' do
+ submit_time('/estimate 3w 1d 1h')
+ submit_time('/remove_estimate')
+
+ page.within '.time-tracking-component-wrap' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'updates the sidebar component when spent is removed' do
+ submit_time('/spend 3w 1d 1h')
+ submit_time('/remove_time_spent')
+
+ page.within '.time-tracking-component-wrap' do
+ expect(page).to have_content 'No estimate or time spent'
+ end
+ end
+
+ it 'shows the help state when icon is clicked' do
+ page.within '.time-tracking-component-wrap' do
+ find('.help-button').click
+ expect(page).to have_content 'Track time with quick actions'
+ expect(page).to have_content 'Learn more'
+ end
+ end
+
+ it 'hides the help state when close icon is clicked' do
+ page.within '.time-tracking-component-wrap' do
+ find('.help-button').click
+ find('.close-help-button').click
+
+ expect(page).not_to have_content 'Track time with quick actions'
+ expect(page).not_to have_content 'Learn more'
+ end
+ end
+
+ it 'displays the correct help url' do
+ page.within '.time-tracking-component-wrap' do
+ find('.help-button').click
+
+ expect(find_link('Learn more')[:href]).to have_content('/help/workflow/time_tracking.md')
+ end
+ end
+end
+
+def submit_time(quick_action)
+ fill_in 'note[note]', with: quick_action
+ find('.js-comment-submit-button').click
+ wait_for_requests
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..93a69093dde
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/title_quick_action_shared_examples.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+shared_examples 'title quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets title quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/title new title"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(issuable.title).to eq 'bug 345'
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the title quick action accordingly' do
+ add_note('/title New title')
+
+ wait_for_requests
+ expect(page).not_to have_content '/title new title'
+ expect(page).to have_content 'Commands applied'
+ expect(page).to have_content 'New title'
+
+ issuable.reload
+ expect(issuable.title).to eq 'New title'
+ end
+
+ context "when current user cannot set title #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not set title to the #{issuable_type}" do
+ add_note('/title New title')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(issuable.title).not_to eq 'New title'
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains title quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/title New title')
+ wait_for_requests
+
+ expect(page).not_to have_content '/title New title'
+ expect(page).to have_content 'Changes the title to "New title".'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..cccc28127ce
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/todo_quick_action_shared_examples.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+shared_examples 'todo quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets todo quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/todo"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+
+ todos = TodosFinder.new(maintainer).execute
+ expect(todos.size).to eq 0
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the todo quick action accordingly' do
+ add_note('/todo')
+
+ wait_for_requests
+ expect(page).not_to have_content '/todo'
+ expect(page).to have_content 'Commands applied'
+
+ todos = TodosFinder.new(maintainer).execute
+ todo = todos.first
+
+ expect(todos.size).to eq 1
+ expect(todo).to be_pending
+ expect(todo.target).to eq issuable
+ expect(todo.author).to eq maintainer
+ expect(todo.user).to eq maintainer
+ end
+
+ context "when current user cannot add todo #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not add todo the #{issuable_type}" do
+ add_note('/todo')
+
+ expect(page).not_to have_content 'Commands applied'
+ todos = TodosFinder.new(maintainer).execute
+ expect(todos.size).to eq 0
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains todo quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/todo')
+
+ expect(page).not_to have_content '/todo'
+ expect(page).to have_content "Adds a todo."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..0b1a52bc860
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/unassign_quick_action_shared_examples.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+shared_examples 'unassign quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets unassign quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/unassign @bob"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.assignees).to eq []
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+
+ it "creates the #{issuable_type} and interprets unassign quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/unassign me"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable.assignees).to eq []
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the unassign quick action accordingly' do
+ assignee = create(:user, username: 'bob')
+ issuable.update(assignee_ids: [assignee.id])
+ expect(issuable.assignees).to eq [assignee]
+
+ add_note("Awesome!\n\n/unassign @bob")
+
+ expect(page).to have_content 'Awesome!'
+ expect(page).not_to have_content '/unassign @bob'
+
+ wait_for_requests
+ issuable.reload
+ note = issuable.notes.user.first
+
+ expect(note.note).to eq 'Awesome!'
+ expect(issuable.assignees).to eq []
+ end
+
+ it "unassigns the #{issuable_type} from current user" do
+ issuable.update(assignee_ids: [maintainer.id])
+ expect(issuable.reload.assignees).to eq [maintainer]
+ expect(issuable.assignees).to eq [maintainer]
+
+ add_note("/unassign me")
+
+ expect(page).not_to have_content '/unassign me'
+ expect(page).to have_content 'Commands applied'
+
+ expect(issuable.reload.assignees).to eq []
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains unassign quick action: from bob' do
+ assignee = create(:user, username: 'bob')
+ issuable.update(assignee_ids: [assignee.id])
+ expect(issuable.assignees).to eq [assignee]
+
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "Awesome!\n/unassign @bob "
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/unassign @bob'
+ expect(page).to have_content 'Awesome!'
+ expect(page).to have_content 'Removes assignee @bob.'
+ end
+ end
+
+ it 'explains unassign quick action: from me' do
+ issuable.update(assignee_ids: [maintainer.id])
+ expect(issuable.assignees).to eq [maintainer]
+
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ page.within('.js-main-target-form') do
+ fill_in 'note[note]', with: "Awesome!\n/unassign me"
+ click_on 'Preview'
+
+ expect(page).not_to have_content '/unassign me'
+ expect(page).to have_content 'Awesome!'
+ expect(page).to have_content "Removes assignee @#{maintainer.username}."
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..1a1ee05841f
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/unlabel_quick_action_shared_examples.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+shared_examples 'unlabel quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets unlabel quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/label ~bug /unlabel"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.labels).to eq [label_bug]
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.update(labels: [label_bug, label_feature])
+ end
+
+ it 'creates the note and interprets the unlabel all quick action accordingly' do
+ add_note("/unlabel")
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlabel'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to eq []
+ end
+
+ it 'creates the note and interprets the unlabel some quick action accordingly' do
+ add_note("/unlabel ~bug")
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlabel'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload.labels).to match_array([label_feature])
+ end
+
+ context "when current user cannot unlabel to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'does not unlabel' do
+ add_note("/unlabel")
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlabel'
+ expect(issuable.labels).to match_array([label_bug, label_feature])
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ before do
+ issuable.update(labels: [label_bug, label_feature])
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ end
+
+ it 'explains unlabel all quick action' do
+ preview_note('/unlabel')
+
+ expect(page).not_to have_content '/unlabel'
+ expect(page).to have_content 'Removes all labels.'
+ end
+
+ it 'explains unlabel some quick action' do
+ preview_note('/unlabel ~bug')
+
+ expect(page).not_to have_content '/unlabel'
+ expect(page).to have_content 'Removes bug label.'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..998ff99b32e
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/unlock_quick_action_shared_examples.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+shared_examples 'unlock quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets unlock quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/unlock"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable).not_to be_discussion_locked
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ issuable.update(discussion_locked: true)
+ expect(issuable).to be_discussion_locked
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it 'creates the note and interprets the unlock quick action accordingly' do
+ add_note('/unlock')
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlock'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.reload).not_to be_discussion_locked
+ end
+
+ context "when current user cannot unlock to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not lock the #{issuable_type}" do
+ add_note('/unlock')
+
+ wait_for_requests
+ expect(page).not_to have_content '/unlock'
+ expect(issuable).to be_discussion_locked
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains unlock quick action' do
+ issuable.update(discussion_locked: true)
+ expect(issuable).to be_discussion_locked
+
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+
+ preview_note('/unlock')
+
+ expect(page).not_to have_content '/unlock'
+ expect(page).to have_content 'Unlocks the discussion'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..bd92f133889
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issuable/unsubscribe_quick_action_shared_examples.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+shared_examples 'unsubscribe quick action' do |issuable_type|
+ before do
+ project.add_maintainer(maintainer)
+ gitlab_sign_in(maintainer)
+ end
+
+ context "new #{issuable_type}", :js do
+ before do
+ case issuable_type
+ when :merge_request
+ visit public_send('namespace_project_new_merge_request_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ when :issue
+ visit public_send('new_namespace_project_issue_path', project.namespace, project, new_url_opts)
+ wait_for_all_requests
+ end
+ end
+
+ it "creates the #{issuable_type} and interprets unsubscribe quick action accordingly" do
+ fill_in "#{issuable_type}_title", with: 'bug 345'
+ fill_in "#{issuable_type}_description", with: "bug description\n/unsubscribe"
+ click_button "Submit #{issuable_type}".humanize
+
+ issuable = project.public_send(issuable_type.to_s.pluralize).first
+
+ expect(issuable.description).to eq 'bug description'
+ expect(issuable).to be_opened
+ expect(page).to have_content 'bug 345'
+ expect(page).to have_content 'bug description'
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+ end
+
+ context "post note to existing #{issuable_type}" do
+ before do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ issuable.subscribe(maintainer, project)
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+
+ it 'creates the note and interprets the unsubscribe quick action accordingly' do
+ add_note('/unsubscribe')
+
+ wait_for_requests
+ expect(page).not_to have_content '/unsubscribe'
+ expect(page).to have_content 'Commands applied'
+ expect(issuable.subscribed?(maintainer, project)).to be_falsey
+ end
+
+ context "when current user cannot unsubscribe to #{issuable_type}" do
+ before do
+ guest = create(:user)
+ project.add_guest(guest)
+
+ gitlab_sign_out
+ gitlab_sign_in(guest)
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ wait_for_all_requests
+ end
+
+ it "does not unsubscribe to the #{issuable_type}" do
+ add_note('/unsubscribe')
+
+ wait_for_requests
+ expect(page).not_to have_content '/unsubscribe'
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+ end
+ end
+ end
+
+ context "preview of note on #{issuable_type}", :js do
+ it 'explains unsubscribe quick action' do
+ visit public_send("project_#{issuable_type}_path", project, issuable)
+ issuable.subscribe(maintainer, project)
+ expect(issuable.subscribed?(maintainer, project)).to be_truthy
+
+ preview_note('/unsubscribe')
+
+ expect(page).not_to have_content '/unsubscribe'
+ expect(page).to have_content "Unsubscribes from this #{issuable_type.to_s.humanize.downcase}."
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..6edd20bb024
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/board_move_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'board_move quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..c68e5aee842
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/confidential_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'confidential quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..5bfc3bb222f
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'create_merge_request quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..db3ecccc339
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/due_quick_action_shared_examples.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+shared_examples 'due quick action not available' do
+ it 'does not set the due date' do
+ add_note('/due 2016-08-28')
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content '/due 2016-08-28'
+ end
+end
+
+shared_examples 'due quick action available and date can be added' do
+ it 'sets the due date accordingly' do
+ add_note('/due 2016-08-28')
+
+ expect(page).not_to have_content '/due 2016-08-28'
+ expect(page).to have_content 'Commands applied'
+
+ visit project_issue_path(project, issue)
+
+ page.within '.due_date' do
+ expect(page).to have_content 'Aug 28, 2016'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..24576fe0021
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'duplicate quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..953e67b0423
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'move quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..5904164fcfc
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/issue/remove_due_date_quick_action_shared_examples.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+shared_examples 'remove_due_date action not available' do
+ it 'does not remove the due date' do
+ add_note("/remove_due_date")
+
+ expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content '/remove_due_date'
+ end
+end
+
+shared_examples 'remove_due_date action available and due date can be removed' do
+ it 'removes the due date accordingly' do
+ add_note('/remove_due_date')
+
+ expect(page).not_to have_content '/remove_due_date'
+ expect(page).to have_content 'Commands applied'
+
+ visit project_issue_path(project, issue)
+
+ page.within '.due_date' do
+ expect(page).to have_content 'No due date'
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..31d88183f0d
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'merge quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..ccb4a85325b
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/merge_request/target_branch_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'target_branch quick action' do
+end
diff --git a/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb
new file mode 100644
index 00000000000..6abb12b41b2
--- /dev/null
+++ b/spec/support/shared_examples/quick_actions/merge_request/wip_quick_action_shared_examples.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+shared_examples 'wip quick action' do
+end
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index 533e9d87ea6..9ce9a353913 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -375,7 +375,7 @@ describe ObjectStorage do
describe '#fog_public' do
subject { uploader.fog_public }
- it { is_expected.to eq(false) }
+ it { is_expected.to eq(nil) }
end
describe '.workhorse_authorize' do
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 2c60ccfb754..c6c10001bc5 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -113,4 +113,56 @@ describe 'layouts/nav/sidebar/_project' do
end
end
end
+
+ describe 'ci/cd settings tab' do
+ before do
+ project.update!(archived: project_archived)
+ end
+
+ context 'when project is archived' do
+ let(:project_archived) { true }
+
+ it 'does not show the ci/cd settings tab' do
+ render
+
+ expect(rendered).not_to have_link('CI / CD', href: project_settings_ci_cd_path(project))
+ end
+ end
+
+ context 'when project is active' do
+ let(:project_archived) { false }
+
+ it 'shows the ci/cd settings tab' do
+ render
+
+ expect(rendered).to have_link('CI / CD', href: project_settings_ci_cd_path(project))
+ end
+ end
+ end
+
+ describe 'operations settings tab' do
+ before do
+ project.update!(archived: project_archived)
+ end
+
+ context 'when project is archived' do
+ let(:project_archived) { true }
+
+ it 'does not show the operations settings tab' do
+ render
+
+ expect(rendered).not_to have_link('Operations', href: project_settings_operations_path(project))
+ end
+ end
+
+ context 'when project is active' do
+ let(:project_archived) { false }
+
+ it 'shows the operations settings tab' do
+ render
+
+ expect(rendered).to have_link('Operations', href: project_settings_operations_path(project))
+ end
+ end
+ end
end
diff --git a/spec/workers/admin_email_worker_spec.rb b/spec/workers/admin_email_worker_spec.rb
index 27687f069ea..f72b932423f 100644
--- a/spec/workers/admin_email_worker_spec.rb
+++ b/spec/workers/admin_email_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AdminEmailWorker do
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index 7244ad4f199..368ed3f3db1 100644
--- a/spec/workers/archive_trace_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ArchiveTraceWorker do
diff --git a/spec/workers/authorized_projects_worker_spec.rb b/spec/workers/authorized_projects_worker_spec.rb
index d095138f6b7..4c02278de64 100644
--- a/spec/workers/authorized_projects_worker_spec.rb
+++ b/spec/workers/authorized_projects_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AuthorizedProjectsWorker do
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 3bd072e7125..746c858609f 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BackgroundMigrationWorker, :sidekiq, :clean_gitlab_redis_shared_state do
diff --git a/spec/workers/build_coverage_worker_spec.rb b/spec/workers/build_coverage_worker_spec.rb
index ba20488f663..25686ae68ca 100644
--- a/spec/workers/build_coverage_worker_spec.rb
+++ b/spec/workers/build_coverage_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildCoverageWorker do
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index ccb26849e67..33f327d4a0c 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildFinishedWorker do
diff --git a/spec/workers/build_hooks_worker_spec.rb b/spec/workers/build_hooks_worker_spec.rb
index 97654a93f5c..59b252a8be3 100644
--- a/spec/workers/build_hooks_worker_spec.rb
+++ b/spec/workers/build_hooks_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildHooksWorker do
diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb
index 5eb9709ded9..065aeaf2b65 100644
--- a/spec/workers/build_success_worker_spec.rb
+++ b/spec/workers/build_success_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildSuccessWorker do
diff --git a/spec/workers/build_trace_sections_worker_spec.rb b/spec/workers/build_trace_sections_worker_spec.rb
index 45243f45547..97fc0a2da0c 100644
--- a/spec/workers/build_trace_sections_worker_spec.rb
+++ b/spec/workers/build_trace_sections_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BuildTraceSectionsWorker do
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index 478fb7d2c0f..eca6cf5235f 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::ArchiveTracesCronWorker do
diff --git a/spec/workers/ci/build_schedule_worker_spec.rb b/spec/workers/ci/build_schedule_worker_spec.rb
index 4a3fe84d7f7..647f9763fed 100644
--- a/spec/workers/ci/build_schedule_worker_spec.rb
+++ b/spec/workers/ci/build_schedule_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Ci::BuildScheduleWorker do
diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb
index da32f29fec0..9cc2ad12bfc 100644
--- a/spec/workers/cluster_provision_worker_spec.rb
+++ b/spec/workers/cluster_provision_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ClusterProvisionWorker do
diff --git a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
index 2e2e9afd25a..a9ffdfb085e 100644
--- a/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
+++ b/spec/workers/cluster_wait_for_ingress_ip_address_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ClusterWaitForIngressIpAddressWorker do
diff --git a/spec/workers/concerns/application_worker_spec.rb b/spec/workers/concerns/application_worker_spec.rb
index 901d77178bc..ae5244e2f62 100644
--- a/spec/workers/concerns/application_worker_spec.rb
+++ b/spec/workers/concerns/application_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ApplicationWorker do
diff --git a/spec/workers/concerns/cluster_queue_spec.rb b/spec/workers/concerns/cluster_queue_spec.rb
index 4118b9aa194..732d55dfbde 100644
--- a/spec/workers/concerns/cluster_queue_spec.rb
+++ b/spec/workers/concerns/cluster_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ClusterQueue do
diff --git a/spec/workers/concerns/cronjob_queue_spec.rb b/spec/workers/concerns/cronjob_queue_spec.rb
index c042a52f41f..cf4d47b7500 100644
--- a/spec/workers/concerns/cronjob_queue_spec.rb
+++ b/spec/workers/concerns/cronjob_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CronjobQueue do
diff --git a/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb b/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb
index 4b9aa9a7ef8..200cdffd560 100644
--- a/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/notify_upon_death_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::NotifyUponDeath do
diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
index 9c187bead0a..51b685b5792 100644
--- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ObjectImporter do
diff --git a/spec/workers/concerns/gitlab/github_import/queue_spec.rb b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
index a96f583aff7..d262bc2e05c 100644
--- a/spec/workers/concerns/gitlab/github_import/queue_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Queue do
diff --git a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
index 8de4059c4ae..294eacf09ab 100644
--- a/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/rescheduling_methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ReschedulingMethods do
diff --git a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
index d85a87f2cb0..f9081a875b5 100644
--- a/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/stage_methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::StageMethods do
diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb
index 24c0a3c6a20..78ceafb359f 100644
--- a/spec/workers/concerns/pipeline_background_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_background_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineBackgroundQueue do
diff --git a/spec/workers/concerns/pipeline_queue_spec.rb b/spec/workers/concerns/pipeline_queue_spec.rb
index a312b307fce..eedfceb8bf0 100644
--- a/spec/workers/concerns/pipeline_queue_spec.rb
+++ b/spec/workers/concerns/pipeline_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineQueue do
diff --git a/spec/workers/concerns/project_import_options_spec.rb b/spec/workers/concerns/project_import_options_spec.rb
index 3699fd83a9a..c5fbcfb5fb0 100644
--- a/spec/workers/concerns/project_import_options_spec.rb
+++ b/spec/workers/concerns/project_import_options_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectImportOptions do
diff --git a/spec/workers/concerns/repository_check_queue_spec.rb b/spec/workers/concerns/repository_check_queue_spec.rb
index d2eeecfc9a8..55ed71f124c 100644
--- a/spec/workers/concerns/repository_check_queue_spec.rb
+++ b/spec/workers/concerns/repository_check_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCheckQueue do
diff --git a/spec/workers/concerns/waitable_worker_spec.rb b/spec/workers/concerns/waitable_worker_spec.rb
index ce38cde9208..37fadd6ac02 100644
--- a/spec/workers/concerns/waitable_worker_spec.rb
+++ b/spec/workers/concerns/waitable_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WaitableWorker do
diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_gpg_signature_worker_spec.rb
index f5479e57260..ae09b4b77f1 100644
--- a/spec/workers/create_gpg_signature_worker_spec.rb
+++ b/spec/workers/create_gpg_signature_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CreateGpgSignatureWorker do
diff --git a/spec/workers/create_note_diff_file_worker_spec.rb b/spec/workers/create_note_diff_file_worker_spec.rb
index 0ac946a1232..e35aaa7d593 100644
--- a/spec/workers/create_note_diff_file_worker_spec.rb
+++ b/spec/workers/create_note_diff_file_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CreateNoteDiffFileWorker do
diff --git a/spec/workers/create_pipeline_worker_spec.rb b/spec/workers/create_pipeline_worker_spec.rb
index 02cb0f46cb4..62a17da80c0 100644
--- a/spec/workers/create_pipeline_worker_spec.rb
+++ b/spec/workers/create_pipeline_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe CreatePipelineWorker do
diff --git a/spec/workers/delete_diff_files_worker_spec.rb b/spec/workers/delete_diff_files_worker_spec.rb
index e0edd313922..9f8b20df48e 100644
--- a/spec/workers/delete_diff_files_worker_spec.rb
+++ b/spec/workers/delete_diff_files_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeleteDiffFilesWorker do
diff --git a/spec/workers/delete_merged_branches_worker_spec.rb b/spec/workers/delete_merged_branches_worker_spec.rb
index 39009d9e4b2..a218ca921d9 100644
--- a/spec/workers/delete_merged_branches_worker_spec.rb
+++ b/spec/workers/delete_merged_branches_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeleteMergedBranchesWorker do
diff --git a/spec/workers/delete_user_worker_spec.rb b/spec/workers/delete_user_worker_spec.rb
index 06d9e125105..c963b886e62 100644
--- a/spec/workers/delete_user_worker_spec.rb
+++ b/spec/workers/delete_user_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DeleteUserWorker do
diff --git a/spec/workers/deployments/success_worker_spec.rb b/spec/workers/deployments/success_worker_spec.rb
index ba7d45eca01..1c68922b03d 100644
--- a/spec/workers/deployments/success_worker_spec.rb
+++ b/spec/workers/deployments/success_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Deployments::SuccessWorker do
diff --git a/spec/workers/detect_repository_languages_worker_spec.rb b/spec/workers/detect_repository_languages_worker_spec.rb
index ff3878fbc8e..dbf32555985 100644
--- a/spec/workers/detect_repository_languages_worker_spec.rb
+++ b/spec/workers/detect_repository_languages_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe DetectRepositoryLanguagesWorker do
diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb
index 045135255d6..f8a31fcdee6 100644
--- a/spec/workers/email_receiver_worker_spec.rb
+++ b/spec/workers/email_receiver_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe EmailReceiverWorker, :mailer do
diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
index 05b4fb49ea3..0f87df89c29 100644
--- a/spec/workers/emails_on_push_worker_spec.rb
+++ b/spec/workers/emails_on_push_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EmailsOnPushWorker, :mailer do
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index ebe02373275..8fddd8540ef 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Every Sidekiq worker' do
diff --git a/spec/workers/expire_build_artifacts_worker_spec.rb b/spec/workers/expire_build_artifacts_worker_spec.rb
index 27995cf1611..74d6b5605d1 100644
--- a/spec/workers/expire_build_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_artifacts_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpireBuildArtifactsWorker do
diff --git a/spec/workers/expire_build_instance_artifacts_worker_spec.rb b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
index e1a56c72162..bdb5a3801d9 100644
--- a/spec/workers/expire_build_instance_artifacts_worker_spec.rb
+++ b/spec/workers/expire_build_instance_artifacts_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpireBuildInstanceArtifactsWorker do
diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb
index 1b614342a18..6ac285ca944 100644
--- a/spec/workers/expire_job_cache_worker_spec.rb
+++ b/spec/workers/expire_job_cache_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpireJobCacheWorker do
diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb
index 54c9a69d329..c98b69ef76c 100644
--- a/spec/workers/expire_pipeline_cache_worker_spec.rb
+++ b/spec/workers/expire_pipeline_cache_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpirePipelineCacheWorker do
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 4895a968d6e..2459638c3e6 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fileutils'
require 'spec_helper'
diff --git a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
index fc7aafbc0c9..b1647d8c7df 100644
--- a/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/advance_stage_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::AdvanceStageWorker, :clean_gitlab_redis_shared_state do
diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
index 5b1c6b6010a..42d69ff6166 100644
--- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ImportDiffNoteWorker do
diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
index ab070d6d081..06a573e16b7 100644
--- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ImportIssueWorker do
diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
index 3a30f06bb2d..5110c3ff11b 100644
--- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ImportNoteWorker do
diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
index 3cccd7cab21..d46e381fc51 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::ImportPullRequestWorker do
diff --git a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
index 7ff133f1049..fa4ded8e42f 100644
--- a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::RefreshImportJidWorker do
diff --git a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
index 91e0cddb5d8..35a856802c2 100644
--- a/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::FinishImportWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
index ad6154cc4a4..0c7fc2a164e 100644
--- a/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportBaseDataWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
index ab347f5b75b..5d96f562c30 100644
--- a/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_issues_and_diff_notes_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportIssuesAndDiffNotesWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
index b19884d7991..e7c9dabb292 100644
--- a/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_lfs_objects_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportLfsObjectsWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
index 94cff9e4e80..90590a45900 100644
--- a/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_notes_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportNotesWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
index 1fbb073a34a..15d485f1018 100644
--- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker do
diff --git a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
index adab535ac05..6d47d73b92e 100644
--- a/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_repository_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::GithubImport::Stage::ImportRepositoryWorker do
diff --git a/spec/workers/gitlab_shell_worker_spec.rb b/spec/workers/gitlab_shell_worker_spec.rb
index 6b222af454d..0758cfc4ee2 100644
--- a/spec/workers/gitlab_shell_worker_spec.rb
+++ b/spec/workers/gitlab_shell_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabShellWorker do
diff --git a/spec/workers/gitlab_usage_ping_worker_spec.rb b/spec/workers/gitlab_usage_ping_worker_spec.rb
index 49b4e04dc7c..aff5d112cdd 100644
--- a/spec/workers/gitlab_usage_ping_worker_spec.rb
+++ b/spec/workers/gitlab_usage_ping_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GitlabUsagePingWorker do
diff --git a/spec/workers/group_destroy_worker_spec.rb b/spec/workers/group_destroy_worker_spec.rb
index a170c84ab12..90a4150a31a 100644
--- a/spec/workers/group_destroy_worker_spec.rb
+++ b/spec/workers/group_destroy_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GroupDestroyWorker do
diff --git a/spec/workers/hashed_storage/migrator_worker_spec.rb b/spec/workers/hashed_storage/migrator_worker_spec.rb
index a85f820a3eb..a318cdd003e 100644
--- a/spec/workers/hashed_storage/migrator_worker_spec.rb
+++ b/spec/workers/hashed_storage/migrator_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HashedStorage::MigratorWorker do
diff --git a/spec/workers/hashed_storage/project_migrate_worker_spec.rb b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
index 340e722aa7e..f266c7dbe8c 100644
--- a/spec/workers/hashed_storage/project_migrate_worker_spec.rb
+++ b/spec/workers/hashed_storage/project_migrate_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe HashedStorage::ProjectMigrateWorker, :clean_gitlab_redis_shared_state do
diff --git a/spec/workers/invalid_gpg_signature_update_worker_spec.rb b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
index 5972696515b..4f727469ea8 100644
--- a/spec/workers/invalid_gpg_signature_update_worker_spec.rb
+++ b/spec/workers/invalid_gpg_signature_update_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe InvalidGpgSignatureUpdateWorker do
diff --git a/spec/workers/issue_due_scheduler_worker_spec.rb b/spec/workers/issue_due_scheduler_worker_spec.rb
index 2710267d384..61ad8330840 100644
--- a/spec/workers/issue_due_scheduler_worker_spec.rb
+++ b/spec/workers/issue_due_scheduler_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe IssueDueSchedulerWorker do
diff --git a/spec/workers/mail_scheduler/issue_due_worker_spec.rb b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
index 1026ae5b4bf..fa17775e9f2 100644
--- a/spec/workers/mail_scheduler/issue_due_worker_spec.rb
+++ b/spec/workers/mail_scheduler/issue_due_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MailScheduler::IssueDueWorker do
diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb
index 5cfba01850c..0729c5f9ffb 100644
--- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb
+++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MailScheduler::NotificationServiceWorker do
diff --git a/spec/workers/merge_worker_spec.rb b/spec/workers/merge_worker_spec.rb
index b57c275c770..138a99abde6 100644
--- a/spec/workers/merge_worker_spec.rb
+++ b/spec/workers/merge_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MergeWorker do
diff --git a/spec/workers/migrate_external_diffs_worker_spec.rb b/spec/workers/migrate_external_diffs_worker_spec.rb
new file mode 100644
index 00000000000..88d48cad14b
--- /dev/null
+++ b/spec/workers/migrate_external_diffs_worker_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MigrateExternalDiffsWorker do
+ let(:worker) { described_class.new }
+ let(:diff) { create(:merge_request).merge_request_diff }
+
+ describe '#perform' do
+ it 'migrates the listed diff' do
+ expect_next_instance_of(MergeRequests::MigrateExternalDiffsService) do |instance|
+ expect(instance.diff).to eq(diff)
+ expect(instance).to receive(:execute)
+ end
+
+ worker.perform(diff.id)
+ end
+
+ it 'does nothing if the diff is missing' do
+ diff.destroy
+
+ worker.perform(diff.id)
+ end
+ end
+end
diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb
index 2f21a1321e1..4fbda37e268 100644
--- a/spec/workers/namespaceless_project_destroy_worker_spec.rb
+++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NamespacelessProjectDestroyWorker do
diff --git a/spec/workers/new_issue_worker_spec.rb b/spec/workers/new_issue_worker_spec.rb
index baa8ddb59e5..88a75ce5b70 100644
--- a/spec/workers/new_issue_worker_spec.rb
+++ b/spec/workers/new_issue_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NewIssueWorker do
diff --git a/spec/workers/new_merge_request_worker_spec.rb b/spec/workers/new_merge_request_worker_spec.rb
index c3f29a40d58..d078ddd07d9 100644
--- a/spec/workers/new_merge_request_worker_spec.rb
+++ b/spec/workers/new_merge_request_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe NewMergeRequestWorker do
diff --git a/spec/workers/new_note_worker_spec.rb b/spec/workers/new_note_worker_spec.rb
index 575361c93d4..2966a201a62 100644
--- a/spec/workers/new_note_worker_spec.rb
+++ b/spec/workers/new_note_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe NewNoteWorker do
diff --git a/spec/workers/pages_domain_verification_cron_worker_spec.rb b/spec/workers/pages_domain_verification_cron_worker_spec.rb
index 8f780428c82..9b479da1cb6 100644
--- a/spec/workers/pages_domain_verification_cron_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_cron_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PagesDomainVerificationCronWorker do
diff --git a/spec/workers/pages_domain_verification_worker_spec.rb b/spec/workers/pages_domain_verification_worker_spec.rb
index 372fc95ab4a..2f23dcb487f 100644
--- a/spec/workers/pages_domain_verification_worker_spec.rb
+++ b/spec/workers/pages_domain_verification_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PagesDomainVerificationWorker do
diff --git a/spec/workers/pipeline_hooks_worker_spec.rb b/spec/workers/pipeline_hooks_worker_spec.rb
index 035e329839f..60df08f40da 100644
--- a/spec/workers/pipeline_hooks_worker_spec.rb
+++ b/spec/workers/pipeline_hooks_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineHooksWorker do
diff --git a/spec/workers/pipeline_metrics_worker_spec.rb b/spec/workers/pipeline_metrics_worker_spec.rb
index 896f9e6e7f2..6beecbcd114 100644
--- a/spec/workers/pipeline_metrics_worker_spec.rb
+++ b/spec/workers/pipeline_metrics_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineMetricsWorker do
diff --git a/spec/workers/pipeline_notification_worker_spec.rb b/spec/workers/pipeline_notification_worker_spec.rb
index eb539ffd893..98b0f139fe2 100644
--- a/spec/workers/pipeline_notification_worker_spec.rb
+++ b/spec/workers/pipeline_notification_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineNotificationWorker, :mailer do
diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb
index 86e9d7f6684..d33cf72e51e 100644
--- a/spec/workers/pipeline_process_worker_spec.rb
+++ b/spec/workers/pipeline_process_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineProcessWorker do
diff --git a/spec/workers/pipeline_schedule_worker_spec.rb b/spec/workers/pipeline_schedule_worker_spec.rb
index ff408427926..f23910d23be 100644
--- a/spec/workers/pipeline_schedule_worker_spec.rb
+++ b/spec/workers/pipeline_schedule_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineScheduleWorker do
diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb
index d1c84adda6f..4cbe384b47a 100644
--- a/spec/workers/pipeline_success_worker_spec.rb
+++ b/spec/workers/pipeline_success_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineSuccessWorker do
diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb
index 0b456cfd0da..0225e4a9601 100644
--- a/spec/workers/pipeline_update_worker_spec.rb
+++ b/spec/workers/pipeline_update_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PipelineUpdateWorker do
diff --git a/spec/workers/plugin_worker_spec.rb b/spec/workers/plugin_worker_spec.rb
index 9238a8199bc..ca6c9986131 100644
--- a/spec/workers/plugin_worker_spec.rb
+++ b/spec/workers/plugin_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PluginWorker do
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index caae46a3175..66958a4c116 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PostReceive do
@@ -33,8 +35,8 @@ describe PostReceive do
describe "#process_project_changes" do
context 'empty changes' do
it "does not call any PushService but runs after project hooks" do
- expect(GitPushService).not_to receive(:new)
- expect(GitTagPushService).not_to receive(:new)
+ expect(Git::BranchPushService).not_to receive(:new)
+ expect(Git::TagPushService).not_to receive(:new)
expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) }
described_class.new.perform(gl_repository, key_id, "")
@@ -45,8 +47,8 @@ describe PostReceive do
let!(:key_id) { "" }
it 'returns false' do
- expect(GitPushService).not_to receive(:new)
- expect(GitTagPushService).not_to receive(:new)
+ expect(Git::BranchPushService).not_to receive(:new)
+ expect(Git::TagPushService).not_to receive(:new)
expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be false
end
@@ -60,9 +62,9 @@ describe PostReceive do
context "branches" do
let(:changes) { "123456 789012 refs/heads/tést" }
- it "calls GitPushService" do
- expect_any_instance_of(GitPushService).to receive(:execute).and_return(true)
- expect_any_instance_of(GitTagPushService).not_to receive(:execute)
+ it "calls Git::BranchPushService" do
+ expect_any_instance_of(Git::BranchPushService).to receive(:execute).and_return(true)
+ expect_any_instance_of(Git::TagPushService).not_to receive(:execute)
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
@@ -70,9 +72,9 @@ describe PostReceive do
context "tags" do
let(:changes) { "123456 789012 refs/tags/tag" }
- it "calls GitTagPushService" do
- expect_any_instance_of(GitPushService).not_to receive(:execute)
- expect_any_instance_of(GitTagPushService).to receive(:execute).and_return(true)
+ it "calls Git::TagPushService" do
+ expect_any_instance_of(Git::BranchPushService).not_to receive(:execute)
+ expect_any_instance_of(Git::TagPushService).to receive(:execute).and_return(true)
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
@@ -81,8 +83,8 @@ describe PostReceive do
let(:changes) { "123456 789012 refs/merge-requests/123" }
it "does not call any of the services" do
- expect_any_instance_of(GitPushService).not_to receive(:execute)
- expect_any_instance_of(GitTagPushService).not_to receive(:execute)
+ expect_any_instance_of(Git::BranchPushService).not_to receive(:execute)
+ expect_any_instance_of(Git::TagPushService).not_to receive(:execute)
described_class.new.perform(gl_repository, key_id, base64_changes)
end
end
@@ -125,7 +127,7 @@ describe PostReceive do
allow_any_instance_of(Gitlab::DataBuilder::Repository).to receive(:update).and_return(fake_hook_data)
# silence hooks so we can isolate
allow_any_instance_of(Key).to receive(:post_create_hook).and_return(true)
- allow_any_instance_of(GitPushService).to receive(:execute).and_return(true)
+ allow_any_instance_of(Git::BranchPushService).to receive(:execute).and_return(true)
end
it 'calls SystemHooksService' do
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 2d071c181c2..47bac63511e 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProcessCommitWorker do
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index b9b5445562f..a7353227043 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectCacheWorker do
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index 6132f145f8d..ec40900a5b7 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectDestroyWorker do
diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb
index 8899969c178..8065087796c 100644
--- a/spec/workers/project_export_worker_spec.rb
+++ b/spec/workers/project_export_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ProjectExportWorker do
diff --git a/spec/workers/propagate_service_template_worker_spec.rb b/spec/workers/propagate_service_template_worker_spec.rb
index af1fb80a51d..fb4ced77832 100644
--- a/spec/workers/propagate_service_template_worker_spec.rb
+++ b/spec/workers/propagate_service_template_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PropagateServiceTemplateWorker do
diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb
index ea2b6ae229e..f1eef1923bf 100644
--- a/spec/workers/prune_old_events_worker_spec.rb
+++ b/spec/workers/prune_old_events_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PruneOldEventsWorker do
diff --git a/spec/workers/prune_web_hook_logs_worker_spec.rb b/spec/workers/prune_web_hook_logs_worker_spec.rb
index b3ec71d4a00..e57334967fd 100644
--- a/spec/workers/prune_web_hook_logs_worker_spec.rb
+++ b/spec/workers/prune_web_hook_logs_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PruneWebHookLogsWorker do
diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb
index 3da851de067..2395e6ec947 100644
--- a/spec/workers/reactive_caching_worker_spec.rb
+++ b/spec/workers/reactive_caching_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ReactiveCachingWorker do
diff --git a/spec/workers/rebase_worker_spec.rb b/spec/workers/rebase_worker_spec.rb
index 900332ed6b3..578b8cf7451 100644
--- a/spec/workers/rebase_worker_spec.rb
+++ b/spec/workers/rebase_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RebaseWorker, '#perform' do
diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb
index e3db10ed645..5182f67b4af 100644
--- a/spec/workers/remote_mirror_notification_worker_spec.rb
+++ b/spec/workers/remote_mirror_notification_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RemoteMirrorNotificationWorker, :mailer do
diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb
index 689bc3d27b4..10d9aa37dee 100644
--- a/spec/workers/remove_expired_group_links_worker_spec.rb
+++ b/spec/workers/remove_expired_group_links_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RemoveExpiredGroupLinksWorker do
diff --git a/spec/workers/remove_expired_members_worker_spec.rb b/spec/workers/remove_expired_members_worker_spec.rb
index 058fdf4c009..69a5725bb35 100644
--- a/spec/workers/remove_expired_members_worker_spec.rb
+++ b/spec/workers/remove_expired_members_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RemoveExpiredMembersWorker do
diff --git a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
index 57f83c1dbe9..0e21933a9a5 100644
--- a/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
+++ b/spec/workers/remove_unreferenced_lfs_objects_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RemoveUnreferencedLfsObjectsWorker do
diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb
index 50b93fce2dc..051c6a5d141 100644
--- a/spec/workers/repository_check/batch_worker_spec.rb
+++ b/spec/workers/repository_check/batch_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCheck::BatchWorker do
diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb
index 1c49415d46c..7ad9e287204 100644
--- a/spec/workers/repository_check/clear_worker_spec.rb
+++ b/spec/workers/repository_check/clear_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCheck::ClearWorker do
diff --git a/spec/workers/repository_check/dispatch_worker_spec.rb b/spec/workers/repository_check/dispatch_worker_spec.rb
index 7877429aa8f..03efb6a0a80 100644
--- a/spec/workers/repository_check/dispatch_worker_spec.rb
+++ b/spec/workers/repository_check/dispatch_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCheck::DispatchWorker do
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index f11875cffd1..65e1c5e9d5d 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'fileutils'
diff --git a/spec/workers/repository_cleanup_worker_spec.rb b/spec/workers/repository_cleanup_worker_spec.rb
index 3adae0b6cfa..e58664cf22a 100644
--- a/spec/workers/repository_cleanup_worker_spec.rb
+++ b/spec/workers/repository_cleanup_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryCleanupWorker do
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 31bfe88d0bd..26fd67adfaa 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryForkWorker do
diff --git a/spec/workers/repository_import_worker_spec.rb b/spec/workers/repository_import_worker_spec.rb
index 87ac4bc05c1..b8767af8eee 100644
--- a/spec/workers/repository_import_worker_spec.rb
+++ b/spec/workers/repository_import_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RepositoryImportWorker do
diff --git a/spec/workers/repository_remove_remote_worker_spec.rb b/spec/workers/repository_remove_remote_worker_spec.rb
index 6ddb653d142..6eba5c50960 100644
--- a/spec/workers/repository_remove_remote_worker_spec.rb
+++ b/spec/workers/repository_remove_remote_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe RepositoryRemoveRemoteWorker do
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index b582a3650b6..4de51ecb3e9 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe RepositoryUpdateRemoteMirrorWorker do
diff --git a/spec/workers/run_pipeline_schedule_worker_spec.rb b/spec/workers/run_pipeline_schedule_worker_spec.rb
index 481a84837f9..690af22f4dc 100644
--- a/spec/workers/run_pipeline_schedule_worker_spec.rb
+++ b/spec/workers/run_pipeline_schedule_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RunPipelineScheduleWorker do
diff --git a/spec/workers/schedule_migrate_external_diffs_worker_spec.rb b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
new file mode 100644
index 00000000000..9d6fecc9f4e
--- /dev/null
+++ b/spec/workers/schedule_migrate_external_diffs_worker_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ScheduleMigrateExternalDiffsWorker do
+ include ExclusiveLeaseHelpers
+
+ let(:worker) { described_class.new }
+
+ describe '#perform' do
+ it 'triggers a scan for diffs to migrate' do
+ expect(MergeRequests::MigrateExternalDiffsService).to receive(:enqueue!)
+
+ worker.perform
+ end
+
+ it 'will not run if the lease is already taken' do
+ stub_exclusive_lease_taken('schedule_migrate_external_diffs_worker', timeout: 2.hours)
+
+ expect(MergeRequests::MigrateExternalDiffsService).not_to receive(:enqueue!)
+
+ worker.perform
+ end
+ end
+end
diff --git a/spec/workers/stage_update_worker_spec.rb b/spec/workers/stage_update_worker_spec.rb
index 7bc76c79464..429d42bac29 100644
--- a/spec/workers/stage_update_worker_spec.rb
+++ b/spec/workers/stage_update_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StageUpdateWorker do
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index e09b8e5b964..72de62f1188 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StuckCiJobsWorker do
diff --git a/spec/workers/stuck_import_jobs_worker_spec.rb b/spec/workers/stuck_import_jobs_worker_spec.rb
index e94d2be9850..dcb8e59ed28 100644
--- a/spec/workers/stuck_import_jobs_worker_spec.rb
+++ b/spec/workers/stuck_import_jobs_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StuckImportJobsWorker do
diff --git a/spec/workers/stuck_merge_jobs_worker_spec.rb b/spec/workers/stuck_merge_jobs_worker_spec.rb
index 5aaff27a6b2..09efed6d2cf 100644
--- a/spec/workers/stuck_merge_jobs_worker_spec.rb
+++ b/spec/workers/stuck_merge_jobs_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe StuckMergeJobsWorker do
diff --git a/spec/workers/system_hook_push_worker_spec.rb b/spec/workers/system_hook_push_worker_spec.rb
index b1d446ed25f..890a622d11a 100644
--- a/spec/workers/system_hook_push_worker_spec.rb
+++ b/spec/workers/system_hook_push_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemHookPushWorker do
diff --git a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
index 9d7c0b8f560..18876b71615 100644
--- a/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
+++ b/spec/workers/todos_destroyer/confidential_issue_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::ConfidentialIssueWorker do
diff --git a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
index 955447906aa..cb14fac0910 100644
--- a/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
+++ b/spec/workers/todos_destroyer/entity_leave_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::EntityLeaveWorker do
diff --git a/spec/workers/todos_destroyer/group_private_worker_spec.rb b/spec/workers/todos_destroyer/group_private_worker_spec.rb
index fcc38989ced..d9a240136d5 100644
--- a/spec/workers/todos_destroyer/group_private_worker_spec.rb
+++ b/spec/workers/todos_destroyer/group_private_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::GroupPrivateWorker do
diff --git a/spec/workers/todos_destroyer/private_features_worker_spec.rb b/spec/workers/todos_destroyer/private_features_worker_spec.rb
index 9599f5ee071..abd04acc3bd 100644
--- a/spec/workers/todos_destroyer/private_features_worker_spec.rb
+++ b/spec/workers/todos_destroyer/private_features_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::PrivateFeaturesWorker do
diff --git a/spec/workers/todos_destroyer/project_private_worker_spec.rb b/spec/workers/todos_destroyer/project_private_worker_spec.rb
index 15d926fa9d5..c1bb0438ec3 100644
--- a/spec/workers/todos_destroyer/project_private_worker_spec.rb
+++ b/spec/workers/todos_destroyer/project_private_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TodosDestroyer::ProjectPrivateWorker do
diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb
index c3c6fdcf2d5..6e524085662 100644
--- a/spec/workers/trending_projects_worker_spec.rb
+++ b/spec/workers/trending_projects_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe TrendingProjectsWorker do
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index d20d926f5a0..c4af829a5e2 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UpdateHeadPipelineForMergeRequestWorker do
diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb
index 0b553db0ca4..486dade454a 100644
--- a/spec/workers/update_merge_requests_worker_spec.rb
+++ b/spec/workers/update_merge_requests_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UpdateMergeRequestsWorker do
diff --git a/spec/workers/upload_checksum_worker_spec.rb b/spec/workers/upload_checksum_worker_spec.rb
index 9e50ce15871..7202c8001b4 100644
--- a/spec/workers/upload_checksum_worker_spec.rb
+++ b/spec/workers/upload_checksum_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe UploadChecksumWorker do
diff --git a/spec/workers/wait_for_cluster_creation_worker_spec.rb b/spec/workers/wait_for_cluster_creation_worker_spec.rb
index 0e92b298178..850eba263a7 100644
--- a/spec/workers/wait_for_cluster_creation_worker_spec.rb
+++ b/spec/workers/wait_for_cluster_creation_worker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe WaitForClusterCreationWorker do
diff --git a/yarn.lock b/yarn.lock
index 7123650b003..846e29862f1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -638,12 +638,12 @@
lodash "^4.17.11"
to-fast-properties "^2.0.0"
-"@gitlab/csslab@^1.8.0":
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.8.0.tgz#54a2457fdc80f006665f0e578a5532780954ccfa"
- integrity sha512-RZylRElufH1kwsBQlIDaVcrcXMyD5IEGrU6ABUd8W3LG8/F9jJ4Y3Ys7EPTpK/qFJyx86AutTtFGRxRNlMx85w==
+"@gitlab/csslab@^1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/csslab/-/csslab-1.9.0.tgz#22fca5b1a30cbd9ca46fc6f9485ecbaba4dc300c"
+ integrity sha512-Zjayzokm7E2wgxUR/pxIMocdiBB5XHt2PEemdzD8qD+aQmMpMxSyIEMQk5Jq0Wgv+Rd5WXTolTw3kmb9l8ZeJg==
dependencies:
- bootstrap "4.1.3"
+ bootstrap "^4.1.3"
"@gitlab/eslint-config@^1.4.0":
version "1.4.0"
@@ -658,10 +658,10 @@
eslint-plugin-promise "^4.0.1"
eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.54.0":
- version "1.54.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf"
- integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ==
+"@gitlab/svgs@^1.57.0":
+ version "1.57.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.57.0.tgz#969ac7bf16337d5de3808fee6fb5c13eefd99478"
+ integrity sha512-AAVvPDaxCsojmOyVVTyaOcob+bPhtYJ+GbtmmNNCHg2dXYDAEgy3+TYzAfV5fQ08TCZ9DPiKEjDIi2ODh0x/8g==
"@gitlab/ui@^3.0.0":
version "3.0.0"
@@ -707,10 +707,10 @@
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==
-"@types/glob@^5":
- version "5.0.35"
- resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a"
- integrity sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==
+"@types/glob@5 - 7":
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
+ integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
dependencies:
"@types/events" "*"
"@types/minimatch" "*"
@@ -791,7 +791,7 @@
resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==
-"@vue/component-compiler-utils@^2.0.0", "@vue/component-compiler-utils@^2.4.0":
+"@vue/component-compiler-utils@^2.4.0", "@vue/component-compiler-utils@^2.5.1":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.6.0.tgz#aa46d2a6f7647440b0b8932434d22f12371e543b"
integrity sha512-IHjxt7LsOFYc0DkTncB7OXJL7UzwOLPPQCfEUNyxL2qt+tF12THV+EO33O1G2Uk4feMSWua3iD39Itszx0f0bw==
@@ -1356,6 +1356,11 @@ async-each@^1.0.0:
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
integrity sha1-GdOGodntxufByF04iu28xW0zYC0=
+async-foreach@^0.1.3:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
+ integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=
+
async-limiter@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
@@ -1619,6 +1624,13 @@ blob@0.0.4:
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=
+block-stream@*:
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
+ integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
+ dependencies:
+ inherits "~2.0.0"
+
bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@~3.5.0:
version "3.5.3"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
@@ -1669,10 +1681,10 @@ bootstrap-vue@^2.0.0-rc.11:
popper.js "^1.12.9"
vue-functional-data-merge "^2.0.5"
-bootstrap@4.1.3, bootstrap@^4.1.1:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.3.tgz#0eb371af2c8448e8c210411d0cb824a6409a12be"
- integrity sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w==
+bootstrap@4.3.1, bootstrap@^4.1.1, bootstrap@^4.1.3:
+ version "4.3.1"
+ resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
+ integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
boxen@^1.2.1:
version "1.3.0"
@@ -1957,6 +1969,14 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3"
integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw==
+camelcase-keys@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
+ integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc=
+ dependencies:
+ camelcase "^2.0.0"
+ map-obj "^1.0.0"
+
camelcase-keys@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77"
@@ -1966,6 +1986,16 @@ camelcase-keys@^4.0.0:
map-obj "^2.0.0"
quick-lru "^1.0.0"
+camelcase@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
+ integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
+
+camelcase@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
+ integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo=
+
camelcase@^4.0.0, camelcase@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
@@ -2010,7 +2040,7 @@ ccount@^1.0.0:
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff"
integrity sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==
-chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3:
+chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
@@ -2060,7 +2090,7 @@ chardet@^0.5.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029"
integrity sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g==
-"charenc@>= 0.0.1":
+"charenc@>= 0.0.1", charenc@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
@@ -2204,6 +2234,15 @@ clipboard@^1.5.5, clipboard@^1.7.1:
select "^1.1.2"
tiny-emitter "^2.0.0"
+cliui@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
+ integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
+ dependencies:
+ string-width "^1.0.1"
+ strip-ansi "^3.0.1"
+ wrap-ansi "^2.0.0"
+
cliui@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.0.0.tgz#743d4650e05f36d1ed2575b59638d87322bfbbcc"
@@ -2572,6 +2611,14 @@ cropper@^2.3.0:
dependencies:
jquery ">= 1.9.1"
+cross-spawn@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982"
+ integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI=
+ dependencies:
+ lru-cache "^4.0.1"
+ which "^1.2.9"
+
cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -2592,7 +2639,7 @@ cross-spawn@^6.0.0, cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
-"crypt@>= 0.0.1":
+"crypt@>= 0.0.1", crypt@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
@@ -2695,6 +2742,11 @@ custom-event@~1.0.0:
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=
+custom-jquery-matchers@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/custom-jquery-matchers/-/custom-jquery-matchers-2.1.0.tgz#e5988fa9715c416b0986b372563f872d9e91e024"
+ integrity sha512-f2vQrncnwqbeDne9ag/BRjrmG9BxIEKnZ9Z8TyG5l9B8wwyINWd7LvuHDlTkMPqLTd3ghcvqFoTsxjhOP371JQ==
+
cyclist@~0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
@@ -3029,7 +3081,7 @@ decamelize-keys@^1.0.0:
decamelize "^1.1.0"
map-obj "^1.0.0"
-decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.2.0:
+decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -4414,6 +4466,16 @@ fsevents@^1.2.2, fsevents@^1.2.3:
nan "^2.9.2"
node-pre-gyp "^0.10.0"
+fstream@^1.0.0, fstream@^1.0.2:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
+ integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=
+ dependencies:
+ graceful-fs "^4.1.2"
+ inherits "~2.0.0"
+ mkdirp ">=0.5 0"
+ rimraf "2"
+
function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -4443,11 +4505,23 @@ gauge@~2.7.3:
strip-ansi "^3.0.1"
wide-align "^1.1.0"
+gaze@^1.0.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a"
+ integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==
+ dependencies:
+ globule "^1.0.0"
+
get-caller-file@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=
+get-stdin@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
+ integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=
+
get-stdin@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
@@ -4477,27 +4551,27 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
-gettext-extractor-vue@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.1.tgz#69d2737eb8f1938803ffcf9317133ed59fb2372f"
- integrity sha512-UnkWVO5jQQrs17L7HSlKj3O7U8C4+AQFzE05MK/I+JkMZdQdB6JMjA0IK0c4GObSlkgx4aiCCG6zWqIBnDR95w==
+gettext-extractor-vue@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/gettext-extractor-vue/-/gettext-extractor-vue-4.0.2.tgz#16e1cdbdaf37e5bdf3cb0aff63685bdc5e74e906"
+ integrity sha512-tnTAU1TdQFREv4Q4hfBDuB329eugeFsYmV7lE9U1jkZEyxcf4oPgimLHNZVNaEUg4+JJwhB8B9HIeqbcbSW32g==
dependencies:
bluebird "^3.5.1"
glob "^7.1.2"
- vue-template-compiler "^2.5.0"
+ vue-template-compiler "^2.5.20"
-gettext-extractor@^3.3.2:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.3.2.tgz#d5172ba8d175678bd40a5abe7f908fa2a9d9473b"
- integrity sha1-1RcrqNF1Z4vUClq+f5CPoqnZRzs=
+gettext-extractor@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/gettext-extractor/-/gettext-extractor-3.4.3.tgz#882679cefc71888eb6e69297e6b2dc14c0384fef"
+ integrity sha512-YSNdTCHmzm58Rc21thtXj7jRIOlqINftM3XbtvNK28C88i35EnEB89iOeV9Vetv7wcb/wiPAtcq/6iSnt2pMyw==
dependencies:
- "@types/glob" "^5"
+ "@types/glob" "5 - 7"
"@types/parse5" "^5"
css-selector-parser "^1.3"
glob "5 - 7"
parse5 "^5"
pofile "^1"
- typescript "^2"
+ typescript "2 - 3"
glob-parent@^3.1.0:
version "3.1.0"
@@ -4512,7 +4586,7 @@ glob-to-regexp@^0.3.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
-"glob@5 - 7", glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3:
+"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.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
@@ -4629,6 +4703,15 @@ globjoin@^0.1.4:
resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43"
integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=
+globule@^1.0.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d"
+ integrity sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==
+ dependencies:
+ glob "~7.1.1"
+ lodash "~4.17.10"
+ minimatch "~3.0.2"
+
gonzales-pe@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.2.3.tgz#41091703625433285e0aee3aa47829fc1fbeb6f2"
@@ -5098,6 +5181,18 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
+in-publish@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51"
+ integrity sha1-4g/146KvwmkDILbcVSaCqcf631E=
+
+indent-string@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
+ integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=
+ dependencies:
+ repeating "^2.0.0"
+
indent-string@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
@@ -5121,7 +5216,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
@@ -5271,7 +5366,7 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
-is-buffer@^1.1.5:
+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==
@@ -5364,6 +5459,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1:
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
+is-finite@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
+ integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=
+ dependencies:
+ number-is-nan "^1.0.0"
+
is-fullwidth-code-point@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
@@ -5532,6 +5634,11 @@ 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:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
+ integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
+
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"
@@ -6090,6 +6197,11 @@ jquery.waitforimages@^2.2.0:
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
+js-base64@^2.1.8:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121"
+ integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==
+
js-beautify@^1.8.8:
version "1.8.9"
resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523"
@@ -6493,6 +6605,17 @@ linkify-it@^2.0.0:
dependencies:
uc.micro "^1.0.1"
+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"
+ integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
+ dependencies:
+ graceful-fs "^4.1.2"
+ parse-json "^2.2.0"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+ strip-bom "^2.0.0"
+
load-json-file@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
@@ -6543,12 +6666,17 @@ locate-path@^3.0.0:
p-locate "^3.0.0"
path-exists "^3.0.0"
+lodash.assign@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
+ integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=
+
lodash.camelcase@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
-lodash.clonedeep@^4.5.0:
+lodash.clonedeep@^4.3.2, lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
@@ -6603,7 +6731,7 @@ lodash.upperfirst@4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0:
+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.3.0, 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==
@@ -6720,7 +6848,7 @@ map-cache@^0.2.2:
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
-map-obj@^1.0.0:
+map-obj@^1.0.0, map-obj@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
@@ -6776,6 +6904,15 @@ md5.js@^1.3.4:
hash-base "^3.0.0"
inherits "^2.0.1"
+md5@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
+ integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=
+ dependencies:
+ charenc "~0.0.1"
+ crypt "~0.0.1"
+ is-buffer "~1.1.1"
+
mdast-util-compact@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz#c12ebe16fffc84573d3e19767726de226e95f649"
@@ -6822,6 +6959,22 @@ memory-fs@^0.4.0, memory-fs@~0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
+meow@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
+ integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=
+ dependencies:
+ camelcase-keys "^2.0.0"
+ decamelize "^1.1.2"
+ loud-rejection "^1.0.0"
+ map-obj "^1.0.1"
+ minimist "^1.1.3"
+ normalize-package-data "^2.3.4"
+ object-assign "^4.0.1"
+ read-pkg-up "^1.0.1"
+ redent "^1.0.0"
+ trim-newlines "^1.0.0"
+
meow@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-5.0.0.tgz#dfc73d63a9afc714a5e371760eb5c88b91078aa4"
@@ -6954,7 +7107,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
-"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
+"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -6979,7 +7132,7 @@ minimist@1.1.x:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=
-minimist@1.2.0, minimist@^1.1.1, minimist@^1.2.0:
+minimist@1.2.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=
@@ -7023,7 +7176,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mkdirp@0.5.x, mkdirp@0.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
+mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -7092,10 +7245,10 @@ mute-stream@0.0.7:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
-nan@^2.9.2:
- version "2.10.0"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
- integrity sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==
+nan@^2.10.0, nan@^2.9.2:
+ version "2.13.1"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.1.tgz#a15bee3790bde247e8f38f1d446edcdaeb05f2dd"
+ integrity sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA==
nanomatch@^1.2.9:
version "1.2.9"
@@ -7157,6 +7310,24 @@ node-forge@0.6.33:
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc"
integrity sha1-RjgRh59XPUUVWtap9D3ClujoXrw=
+node-gyp@^3.8.0:
+ version "3.8.0"
+ resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
+ integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==
+ dependencies:
+ fstream "^1.0.0"
+ glob "^7.0.3"
+ graceful-fs "^4.1.2"
+ mkdirp "^0.5.0"
+ nopt "2 || 3"
+ npmlog "0 || 1 || 2 || 3 || 4"
+ osenv "0"
+ request "^2.87.0"
+ rimraf "2"
+ semver "~5.3.0"
+ tar "^2.0.0"
+ which "1"
+
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -7229,6 +7400,31 @@ node-releases@^1.1.3:
dependencies:
semver "^5.3.0"
+node-sass@^4.11.0:
+ version "4.11.0"
+ resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a"
+ integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==
+ dependencies:
+ async-foreach "^0.1.3"
+ chalk "^1.1.1"
+ cross-spawn "^3.0.0"
+ gaze "^1.0.0"
+ get-stdin "^4.0.1"
+ glob "^7.0.3"
+ in-publish "^2.0.0"
+ lodash.assign "^4.2.0"
+ lodash.clonedeep "^4.3.2"
+ lodash.mergewith "^4.6.0"
+ meow "^3.7.0"
+ mkdirp "^0.5.1"
+ nan "^2.10.0"
+ node-gyp "^3.8.0"
+ npmlog "^4.0.0"
+ request "^2.88.0"
+ sass-graph "^2.2.4"
+ stdout-stream "^1.4.0"
+ "true-case-path" "^1.0.2"
+
nodemon@^1.18.9:
version "1.18.9"
resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.9.tgz#90b467efd3b3c81b9453380aeb2a2cba535d0ead"
@@ -7245,7 +7441,7 @@ nodemon@^1.18.9:
undefsafe "^2.0.2"
update-notifier "^2.5.0"
-nopt@3.x:
+"nopt@2 || 3", nopt@3.x:
version "3.0.6"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k=
@@ -7328,7 +7524,7 @@ npm-run-path@^2.0.0:
dependencies:
path-key "^2.0.0"
-npmlog@^4.0.2:
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -7541,6 +7737,13 @@ os-homedir@^1.0.0:
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
+os-locale@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
+ integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=
+ 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"
@@ -7564,7 +7767,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
-osenv@^0.1.4:
+osenv@0, osenv@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
@@ -7802,6 +8005,15 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+path-type@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
+ integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
+ dependencies:
+ graceful-fs "^4.1.2"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
@@ -7914,10 +8126,10 @@ pofile@^1:
resolved "https://registry.yarnpkg.com/pofile/-/pofile-1.0.11.tgz#35aff58c17491d127a07336d5522ebc9df57c954"
integrity sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==
-popper.js@^1.12.9, popper.js@^1.14.3:
- version "1.14.3"
- resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095"
- integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU=
+popper.js@^1.12.9, popper.js@^1.14.7:
+ version "1.14.7"
+ resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.7.tgz#e31ec06cfac6a97a53280c3e55e4e0c860e7738e"
+ integrity sha512-4q1hNvoUre/8srWsH7hnoSJ5xVmIL4qgz+s4qf2TnJIMyZFUFMGH+9vE7mXynAlHSZ/NdTmmow86muD0myUkVQ==
portfinder@^1.0.9:
version "1.0.13"
@@ -8463,6 +8675,14 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
+read-pkg-up@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
+ integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=
+ dependencies:
+ find-up "^1.0.0"
+ read-pkg "^1.0.0"
+
read-pkg-up@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
@@ -8487,6 +8707,15 @@ read-pkg-up@^4.0.0:
find-up "^3.0.0"
read-pkg "^3.0.0"
+read-pkg@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
+ integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=
+ dependencies:
+ load-json-file "^1.0.0"
+ normalize-package-data "^2.3.2"
+ path-type "^1.0.0"
+
read-pkg@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
@@ -8556,6 +8785,14 @@ realpath-native@^1.0.0, realpath-native@^1.0.2:
dependencies:
util.promisify "^1.0.0"
+redent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
+ integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=
+ dependencies:
+ indent-string "^2.1.0"
+ strip-indent "^1.0.1"
+
redent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa"
@@ -8745,6 +8982,13 @@ repeat-string@^1.5.4, repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
+repeating@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
+ integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
+ dependencies:
+ is-finite "^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"
@@ -8766,7 +9010,7 @@ request-promise-native@^1.0.5:
stealthy-require "^1.1.0"
tough-cookie ">=2.3.3"
-request@^2.87.0:
+request@^2.87.0, request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -8894,7 +9138,7 @@ rfdc@^1.1.2:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==
-rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
+rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -9002,6 +9246,16 @@ sanitize-html@^1.16.1:
srcset "^1.0.0"
xtend "^4.0.0"
+sass-graph@^2.2.4:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49"
+ integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=
+ dependencies:
+ glob "^7.0.0"
+ lodash "^4.0.0"
+ scss-tokenizer "^0.2.3"
+ yargs "^7.0.0"
+
sax@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
@@ -9033,6 +9287,14 @@ scope-css@^1.0.5:
slugify "^1.3.1"
strip-css-comments "^3.0.0"
+scss-tokenizer@^0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
+ integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE=
+ dependencies:
+ js-base64 "^2.1.8"
+ source-map "^0.4.2"
+
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
@@ -9067,6 +9329,11 @@ semver-diff@^2.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
+semver@~5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
+ integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8=
+
send@0.16.2:
version "0.16.2"
resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
@@ -9384,6 +9651,13 @@ source-map@0.5.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86"
integrity sha1-D+llA6yGpa213mP05BKuSHLNvoY=
+source-map@^0.4.2:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
+ integrity sha1-66T12pwNyZneaAMti092FzZSA2s=
+ dependencies:
+ amdefine ">=0.0.4"
+
source-map@^0.5.0, source-map@^0.5.6:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
@@ -9521,6 +9795,13 @@ statuses@~1.3.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
integrity sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=
+stdout-stream@^1.4.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de"
+ integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==
+ dependencies:
+ readable-stream "^2.0.1"
+
stealthy-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
@@ -9655,6 +9936,13 @@ strip-ansi@^5.0.0:
dependencies:
ansi-regex "^4.0.0"
+strip-bom@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
+ integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
+ dependencies:
+ is-utf8 "^0.2.0"
+
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@@ -9672,6 +9960,13 @@ strip-eof@^1.0.0:
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
+strip-indent@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
+ integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=
+ dependencies:
+ get-stdin "^4.0.1"
+
strip-indent@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
@@ -9705,10 +10000,10 @@ stylelint-error-string-formatter@^1.0.1:
resolved "https://registry.yarnpkg.com/stylelint-error-string-formatter/-/stylelint-error-string-formatter-1.0.1.tgz#366387825d6fb59569e8c5c3f5682398733756f9"
integrity sha512-8zy0UsdnQZKVDwjWMQX36b30TaNMGcM2FzBcK9cshpXerpJ3AvF2/zw7FJ3Efm6DFviTBVsxR14F3FnDFhCxJw==
-stylelint-scss@^3.5.3:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.5.3.tgz#e158b3061eeec26d7f6088f346998a797432f3c8"
- integrity sha512-QESQUOY1ldU5tlJTTM3Megz/QtJ39S58ByjZ7dZobGDq9qMjy5jbC7PDUasrv/T7pB1UbpPojpxX9K1OR7IPEg==
+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==
dependencies:
lodash "^4.17.11"
postcss-media-query-parser "^0.2.3"
@@ -9847,6 +10142,15 @@ tapable@^1.0.0, tapable@^1.1.0:
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.0.tgz#0d076a172e3d9ba088fd2272b2668fb8d194b78c"
integrity sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==
+tar@^2.0.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
+ integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=
+ dependencies:
+ block-stream "*"
+ fstream "^1.0.2"
+ inherits "2"
+
tar@^4:
version "4.4.4"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.4.tgz#ec8409fae9f665a4355cc3b4087d0820232bb8cd"
@@ -10107,6 +10411,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+trim-newlines@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
+ integrity sha1-WIeWa7WCpFA6QetST301ARgVphM=
+
trim-newlines@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20"
@@ -10132,6 +10441,13 @@ trough@^1.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24"
integrity sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw==
+"true-case-path@^1.0.2":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d"
+ integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==
+ dependencies:
+ glob "^7.1.2"
+
tryer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7"
@@ -10201,10 +10517,10 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typescript@^2:
- version "2.9.2"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c"
- integrity sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==
+"typescript@2 - 3":
+ version "3.3.4000"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0"
+ integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA==
uc.micro@^1.0.1, uc.micro@^1.0.5:
version "1.0.5"
@@ -10627,12 +10943,12 @@ vue-jest@^4.0.0-beta.2:
source-map "^0.5.6"
ts-jest "^23.10.5"
-vue-loader@^15.4.2:
- version "15.4.2"
- resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.4.2.tgz#812bb26e447dd3b84c485eb634190d914ce125e2"
- integrity sha512-nVV27GNIA9MeoD8yQ3dkUzwlAaAsWeYSWZHsu/K04KCD339lW0Jv2sJWsjj3721SP7sl2lYdPmjcHgkWQSp5bg==
+vue-loader@^15.4.2, vue-loader@^15.7.0:
+ version "15.7.0"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.0.tgz#27275aa5a3ef4958c5379c006dd1436ad04b25b3"
+ integrity sha512-x+NZ4RIthQOxcFclEcs8sXGEWqnZHodL2J9Vq+hUz+TDZzBaDIh1j3d9M2IUlTjtrHTZy4uMuRdTi8BGws7jLA==
dependencies:
- "@vue/component-compiler-utils" "^2.0.0"
+ "@vue/component-compiler-utils" "^2.5.1"
hash-sum "^1.0.2"
loader-utils "^1.1.0"
vue-hot-reload-api "^2.3.0"
@@ -10658,10 +10974,10 @@ vue-style-loader@^4.1.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@^2.5.0, vue-template-compiler@^2.5.21:
- version "2.5.21"
- resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
- integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw==
+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==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
@@ -10671,20 +10987,20 @@ vue-template-es2015-compiler@^1.9.0:
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
-vue-virtual-scroll-list@^1.2.5:
- version "1.2.5"
- resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.2.5.tgz#bcbd010f7cdb035eba8958ebf807c6214d9a167a"
- integrity sha1-vL0BD3zbA166iVjr+AfGIU2aFno=
+vue-virtual-scroll-list@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/vue-virtual-scroll-list/-/vue-virtual-scroll-list-1.3.1.tgz#efcb83d3a3dcc69cd886fa4de1130a65493e8f76"
+ integrity sha512-PMTxiK9/P1LtgoWWw4n1QnmDDkYqIdWWCNdt1L4JD9g6rwDgnsGsSV10bAnd5n7DQLHGWHjRex+zAbjXWT8t0g==
-vue@^2.5.21:
- version "2.5.21"
- resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
- integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ==
+vue@^2.5.21, vue@^2.6.10:
+ version "2.6.10"
+ resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.10.tgz#a72b1a42a4d82a721ea438d1b6bf55e66195c637"
+ integrity sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==
-vuex@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.0.1.tgz#e761352ebe0af537d4bb755a9b9dc4be3df7efd2"
- integrity sha512-wLoqz0B7DSZtgbWL1ShIBBCjv22GV5U+vcBFox658g6V0s4wZV9P4YjCNyoHSyIBpj1f29JBoNQIqD82cR4O3w==
+vuex@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9"
+ integrity sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg==
w3c-hr-time@^1.0.1:
version "1.0.1"
@@ -10910,12 +11226,17 @@ whatwg-url@^7.0.0:
tr46 "^1.0.1"
webidl-conversions "^4.0.2"
+which-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
+ integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=
+
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
-which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
+which@1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -11111,6 +11432,13 @@ yargs-parser@^11.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"
+yargs-parser@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
+ integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=
+ 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"
@@ -11172,6 +11500,25 @@ yargs@^12.0.2, yargs@^12.0.4:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^11.1.1"
+yargs@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
+ integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=
+ dependencies:
+ camelcase "^3.0.0"
+ cliui "^3.2.0"
+ decamelize "^1.1.1"
+ get-caller-file "^1.0.1"
+ os-locale "^1.4.0"
+ read-pkg-up "^1.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^1.0.1"
+ set-blocking "^2.0.0"
+ string-width "^1.0.2"
+ which-module "^1.0.0"
+ y18n "^3.2.1"
+ yargs-parser "^5.0.0"
+
yarn-deduplicate@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-1.1.1.tgz#19b4a87654b66f55bf3a4bd6b153b4e4ab1b6e6d"