summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/CODEOWNERS2
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml9
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml14
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml16
-rw-r--r--.gitlab/ci/memory.gitlab-ci.yml23
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml53
-rw-r--r--.gitlab/ci/setup.gitlab-ci.yml14
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml14
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock10
-rw-r--r--app/assets/javascripts/boards/components/board.js40
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue22
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue13
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue25
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue30
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue11
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue7
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue4
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue9
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.vue2
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js2
-rw-r--r--app/assets/javascripts/boards/models/list.js8
-rw-r--r--app/assets/javascripts/branches/divergence_graph.js62
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue4
-rw-r--r--app/assets/javascripts/clusters/components/knative_domain_editor.vue10
-rw-r--r--app/assets/javascripts/clusters/components/uninstall_application_button.vue3
-rw-r--r--app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue4
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue12
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue8
-rw-r--r--app/assets/javascripts/environments/components/environment_monitoring.vue3
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.vue3
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue9
-rw-r--r--app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue6
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue34
-rw-r--r--app/assets/javascripts/lib/graphql.js2
-rw-r--r--app/assets/javascripts/notes/components/discussion_actions.vue31
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue5
-rw-r--r--app/assets/javascripts/notes/stores/getters.js6
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue5
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue72
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/constants.js27
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/mixins/settings_pannel_mixin.js26
-rw-r--r--app/assets/javascripts/performance_bar/components/detailed_metric.vue4
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue3
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue6
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js6
-rw-r--r--app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue4
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue6
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue135
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue4
-rw-r--r--app/assets/javascripts/repository/index.js30
-rw-r--r--app/assets/javascripts/repository/queries/pathLastCommit.query.graphql6
-rw-r--r--app/assets/javascripts/serverless/components/area.vue15
-rw-r--r--app/assets/javascripts/serverless/components/function_details.vue4
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue47
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue2
-rw-r--r--app/assets/stylesheets/application.scss8
-rw-r--r--app/assets/stylesheets/csslab.scss2
-rw-r--r--app/assets/stylesheets/errors.scss12
-rw-r--r--app/assets/stylesheets/framework.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss72
-rw-r--r--app/assets/stylesheets/pages/notes.scss4
-rw-r--r--app/controllers/dashboard/projects_controller.rb1
-rw-r--r--app/controllers/groups_controller.rb3
-rw-r--r--app/controllers/projects/branches_controller.rb38
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb7
-rw-r--r--app/controllers/projects/merge_requests/content_controller.rb22
-rw-r--r--app/controllers/projects/merge_requests_controller.rb6
-rw-r--r--app/finders/branches_finder.rb14
-rw-r--r--app/graphql/mutations/.keep0
-rw-r--r--app/graphql/mutations/award_emojis/add.rb25
-rw-r--r--app/graphql/mutations/award_emojis/base.rb41
-rw-r--r--app/graphql/mutations/award_emojis/remove.rb33
-rw-r--r--app/graphql/mutations/award_emojis/toggle.rb40
-rw-r--r--app/graphql/mutations/base_mutation.rb7
-rw-r--r--app/graphql/types/award_emojis/award_emoji_type.rb46
-rw-r--r--app/graphql/types/commit_type.rb30
-rw-r--r--app/graphql/types/mutation_type.rb3
-rw-r--r--app/graphql/types/tree/tree_type.rb5
-rw-r--r--app/helpers/onboarding_experiment_helper.rb7
-rw-r--r--app/mailers/emails/notes.rb4
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/board.rb5
-rw-r--r--app/models/ci/pipeline.rb5
-rw-r--r--app/models/clusters/applications/jupyter.rb6
-rw-r--r--app/models/concerns/deployable.rb1
-rw-r--r--app/models/concerns/relative_positioning.rb4
-rw-r--r--app/models/deployment.rb18
-rw-r--r--app/models/list.rb1
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb5
-rw-r--r--app/models/project_services/jira_service.rb24
-rw-r--r--app/models/repository.rb40
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/policies/award_emoji_policy.rb11
-rw-r--r--app/presenters/award_emoji_presenter.rb27
-rw-r--r--app/presenters/commit_presenter.rb8
-rw-r--r--app/serializers/merge_request_widget_entity.rb8
-rw-r--r--app/services/branches/diverging_commit_counts_service.rb54
-rw-r--r--app/services/issuable_base_service.rb13
-rw-r--r--app/services/merge_requests/create_from_issue_service.rb36
-rw-r--r--app/services/system_note_service.rb7
-rw-r--r--app/services/users/update_service.rb10
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/notify/new_merge_request_email.text.erb2
-rw-r--r--app/views/projects/_commit_button.html.haml2
-rw-r--r--app/views/projects/_files.html.haml4
-rw-r--r--app/views/projects/_merge_request_settings_description_text.html.haml1
-rw-r--r--app/views/projects/blob/_editor.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml8
-rw-r--r--app/views/projects/branches/index.html.haml1
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/environments/show.html.haml28
-rw-r--r--app/views/projects/tree/_tree_header.html.haml1
-rw-r--r--app/views/search/_category.html.haml1
-rw-r--r--app/views/shared/_confirm_modal.html.haml6
-rw-r--r--app/views/shared/boards/components/_board.html.haml60
-rw-r--r--changelogs/unreleased/11039-moved-code-difference-from-EE-to-CE.yml5
-rw-r--r--changelogs/unreleased/44106-include-subgroups-in-group-activity.yml5
-rw-r--r--changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml5
-rw-r--r--changelogs/unreleased/45120-fix-ide-editor-to-update-size-on-show-change.yml5
-rw-r--r--changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml6
-rw-r--r--changelogs/unreleased/62826-graphql-emoji-mutations.yml5
-rw-r--r--changelogs/unreleased/62968-environment-details-header-border-misaligned.yml5
-rw-r--r--changelogs/unreleased/63479-jira-capitalization.yml5
-rw-r--r--changelogs/unreleased/63590-pipeline-actions-cause-full-refresh.yml5
-rw-r--r--changelogs/unreleased/add-clusters-to-deployment.yml5
-rw-r--r--changelogs/unreleased/add-strategies-column-to-scopes-table.yml5
-rw-r--r--changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml5
-rw-r--r--changelogs/unreleased/check-min-schema-migrate.yml5
-rw-r--r--changelogs/unreleased/feature-uninstall_jupyter_hub_app.yml5
-rw-r--r--changelogs/unreleased/graphql-tree-last-commit.yml5
-rw-r--r--changelogs/unreleased/id-extract-widget-into-different-request.yml5
-rw-r--r--changelogs/unreleased/id-stale-branches.yml5
-rw-r--r--changelogs/unreleased/mh-board-tooltips.yml5
-rw-r--r--changelogs/unreleased/mh-collapsible-boards.yml5
-rw-r--r--changelogs/unreleased/set-higher-ttl-for-trace-write.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-63910.yml5
-rw-r--r--changelogs/unreleased/sh-support-subnets-ip-rate-limiter.yml5
-rw-r--r--changelogs/unreleased/small-s-in-elasticsearch-in-code.yml5
-rw-r--r--changelogs/unreleased/small-s-in-elasticsearch.yml5
-rw-r--r--changelogs/unreleased/support-jsonb-default-value.yml5
-rw-r--r--changelogs/unreleased/tc-rake-orphan-artifacts.yml5
-rw-r--r--changelogs/unreleased/transaction-metrics.yml5
-rw-r--r--config/boot.rb2
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/1_settings.rb144
-rw-r--r--config/initializers/6_validations.rb21
-rw-r--r--config/initializers/7_prometheus_metrics.rb15
-rw-r--r--config/initializers/jira.rb2
-rw-r--r--config/initializers/transaction_metrics.rb3
-rw-r--r--config/routes/project.rb1
-rw-r--r--config/routes/repository.rb5
-rw-r--r--config/unicorn.rb.example16
-rw-r--r--config/unicorn.rb.example.development16
-rw-r--r--db/fixtures/development/24_forks.rb4
-rw-r--r--db/migrate/20190613073003_create_project_aliases.rb16
-rw-r--r--db/migrate/20190623212503_add_cluster_id_to_deployments.rb9
-rw-r--r--db/migrate/20190627051902_add_cluster_id_index_fk_to_deployments.rb21
-rw-r--r--db/migrate/20190628145246_add_strategies_to_operations_feature_flag_scopes.rb17
-rw-r--r--db/schema.rb16
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/database_load_balancing.md18
-rw-r--r--doc/administration/geo/replication/high_availability.md26
-rw-r--r--doc/administration/geo/replication/troubleshooting.md9
-rw-r--r--doc/administration/gitaly/index.md2
-rw-r--r--doc/administration/logs.md2
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/project_aliases.md105
-rw-r--r--doc/api/services.md26
-rw-r--r--doc/ci/docker/using_docker_images.md105
-rw-r--r--doc/ci/merge_request_pipelines/index.md60
-rw-r--r--doc/development/architecture.md14
-rw-r--r--doc/development/contributing/issue_workflow.md33
-rw-r--r--doc/development/contributing/style_guides.md4
-rw-r--r--doc/development/database_debugging.md18
-rw-r--r--doc/development/documentation/site_architecture/index.md42
-rw-r--r--doc/development/documentation/styleguide.md23
-rw-r--r--doc/development/ee_features.md4
-rw-r--r--doc/development/elasticsearch.md10
-rw-r--r--doc/development/fe_guide/vue.md2
-rw-r--r--doc/development/gitaly.md12
-rw-r--r--doc/development/i18n/externalization.md45
-rw-r--r--doc/development/testing_guide/best_practices.md2
-rw-r--r--doc/development/testing_guide/review_apps.md2
-rw-r--r--doc/install/openshift_and_gitlab/index.md12
-rw-r--r--doc/integration/README.md4
-rw-r--r--doc/integration/elasticsearch.md2
-rw-r--r--doc/integration/jenkins_deprecated.md8
-rw-r--r--doc/push_rules/push_rules.md4
-rw-r--r--doc/raketasks/backup_restore.md315
-rw-r--r--doc/raketasks/cleanup.md45
-rw-r--r--doc/raketasks/web_hooks.md60
-rw-r--r--doc/security/rack_attack.md5
-rw-r--r--doc/topics/application_development_platform/index.md17
-rw-r--r--doc/university/README.md2
-rw-r--r--doc/university/support/README.md2
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md22
-rw-r--r--doc/user/admin_area/index.md65
-rw-r--r--doc/user/admin_area/monitoring/health_check.md51
-rw-r--r--doc/user/application_security/container_scanning/index.md5
-rw-r--r--doc/user/application_security/dast/index.md5
-rw-r--r--doc/user/application_security/dependency_scanning/index.md5
-rw-r--r--doc/user/application_security/index.md25
-rw-r--r--doc/user/application_security/sast/index.md13
-rw-r--r--doc/user/clusters/applications.md2
-rw-r--r--doc/user/group/contribution_analytics/index.md14
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/group/insights/index.md3
-rw-r--r--doc/user/index.md4
-rw-r--r--doc/user/markdown.md28
-rw-r--r--doc/user/permissions.md5
-rw-r--r--doc/user/profile/account/img/2fa.pngbin22047 -> 0 bytes
-rw-r--r--doc/user/profile/account/img/2fa_auth.pngbin14535 -> 0 bytes
-rw-r--r--doc/user/profile/account/img/2fa_u2f_authenticate.pngbin17582 -> 0 bytes
-rw-r--r--doc/user/profile/account/img/2fa_u2f_register.pngbin35186 -> 0 bytes
-rw-r--r--doc/user/profile/account/two_factor_authentication.md150
-rw-r--r--doc/user/project/clusters/index.md18
-rw-r--r--doc/user/project/clusters/serverless/index.md47
-rw-r--r--doc/user/project/deploy_tokens/img/deploy_tokens.pngbin23087 -> 218635 bytes
-rw-r--r--doc/user/project/deploy_tokens/index.md9
-rw-r--r--doc/user/project/index.md23
-rw-r--r--doc/user/project/insights/index.md3
-rw-r--r--doc/user/project/integrations/jira.md14
-rw-r--r--doc/user/project/integrations/jira_cloud_configuration.md12
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md2
-rw-r--r--doc/user/project/integrations/project_services.md2
-rw-r--r--doc/user/project/quick_actions.md1
-rw-r--r--lib/api/boards.rb2
-rw-r--r--lib/api/boards_responses.rb2
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/api/group_boards.rb2
-rw-r--r--lib/api/helpers/services_helpers.rb10
-rw-r--r--lib/banzai/filter/relative_link_filter.rb6
-rw-r--r--lib/gitlab/auth/ip_rate_limiter.rb17
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb2
-rw-r--r--lib/gitlab/ci/trace.rb2
-rw-r--r--lib/gitlab/cleanup/orphan_job_artifact_files.rb132
-rw-r--r--lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb80
-rw-r--r--lib/gitlab/cluster/lifecycle_events.rb3
-rw-r--r--lib/gitlab/database.rb38
-rw-r--r--lib/gitlab/database/migration_helpers.rb3
-rw-r--r--lib/gitlab/graphql/copy_field_description.rb21
-rw-r--r--lib/gitlab/graphql/errors.rb1
-rw-r--r--lib/gitlab/graphql/find_argument_in_parent.rb32
-rw-r--r--lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb25
-rw-r--r--lib/gitlab/metrics/system.rb14
-rw-r--r--lib/gitlab/optimistic_locking.rb1
-rw-r--r--lib/gitlab/search/found_blob.rb2
-rw-r--r--lib/tasks/gitlab/cleanup.rake25
-rw-r--r--lib/tasks/migrate/schema_check.rake20
-rw-r--r--locale/ar_SA/gitlab.po6
-rw-r--r--locale/bg/gitlab.po6
-rw-r--r--locale/bn_BD/gitlab.po6
-rw-r--r--locale/bn_IN/gitlab.po6
-rw-r--r--locale/ca_ES/gitlab.po6
-rw-r--r--locale/cs_CZ/gitlab.po6
-rw-r--r--locale/cy_GB/gitlab.po6
-rw-r--r--locale/da_DK/gitlab.po6
-rw-r--r--locale/de/gitlab.po6
-rw-r--r--locale/el_GR/gitlab.po6
-rw-r--r--locale/eo/gitlab.po6
-rw-r--r--locale/es/gitlab.po6
-rw-r--r--locale/et_EE/gitlab.po6
-rw-r--r--locale/fil_PH/gitlab.po6
-rw-r--r--locale/fr/gitlab.po6
-rw-r--r--locale/gitlab.pot173
-rw-r--r--locale/gl_ES/gitlab.po6
-rw-r--r--locale/he_IL/gitlab.po6
-rw-r--r--locale/hi_IN/gitlab.po6
-rw-r--r--locale/hr_HR/gitlab.po6
-rw-r--r--locale/hu_HU/gitlab.po6
-rw-r--r--locale/id_ID/gitlab.po6
-rw-r--r--locale/it/gitlab.po6
-rw-r--r--locale/ja/gitlab.po6
-rw-r--r--locale/ka_GE/gitlab.po6
-rw-r--r--locale/ko/gitlab.po6
-rw-r--r--locale/mn_MN/gitlab.po6
-rw-r--r--locale/nb_NO/gitlab.po6
-rw-r--r--locale/nl_NL/gitlab.po6
-rw-r--r--locale/pa_IN/gitlab.po6
-rw-r--r--locale/pl_PL/gitlab.po6
-rw-r--r--locale/pt_BR/gitlab.po6
-rw-r--r--locale/pt_PT/gitlab.po6
-rw-r--r--locale/ro_RO/gitlab.po6
-rw-r--r--locale/ru/gitlab.po6
-rw-r--r--locale/sk_SK/gitlab.po6
-rw-r--r--locale/sq_AL/gitlab.po6
-rw-r--r--locale/sr_CS/gitlab.po6
-rw-r--r--locale/sr_SP/gitlab.po6
-rw-r--r--locale/sv_SE/gitlab.po6
-rw-r--r--locale/sw_KE/gitlab.po6
-rw-r--r--locale/tr_TR/gitlab.po6
-rw-r--r--locale/uk/gitlab.po6
-rw-r--r--locale/zh_CN/gitlab.po6
-rw-r--r--locale/zh_HK/gitlab.po6
-rw-r--r--locale/zh_TW/gitlab.po6
-rw-r--r--package.json4
-rw-r--r--qa/qa.rb5
-rw-r--r--qa/qa/page/component/confirm_modal.rb25
-rw-r--r--qa/qa/page/component/select2.rb4
-rw-r--r--qa/qa/page/file/edit.rb13
-rw-r--r--qa/qa/page/file/form.rb27
-rw-r--r--qa/qa/page/file/shared/commit_button.rb21
-rw-r--r--qa/qa/page/file/shared/editor.rb33
-rw-r--r--qa/qa/page/file/show.rb2
-rw-r--r--qa/qa/page/project/settings/advanced.rb16
-rw-r--r--qa/qa/page/project/settings/main.rb2
-rw-r--r--qa/qa/page/project/sub_menus/project.rb29
-rw-r--r--qa/qa/page/project/sub_menus/settings.rb9
-rw-r--r--qa/qa/page/settings/common.rb2
-rw-r--r--qa/qa/resource/api_fabricator.rb7
-rw-r--r--qa/qa/resource/group.rb4
-rw-r--r--qa/qa/resource/merge_request_from_fork.rb2
-rw-r--r--qa/qa/resource/project.rb28
-rw-r--r--qa/qa/resource/user.rb2
-rw-r--r--qa/qa/runtime/api/client.rb19
-rw-r--r--qa/qa/service/shellout.rb2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb75
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb57
-rw-r--r--qa/qa/support/api.rb5
-rw-r--r--qa/spec/runtime/api/client_spec.rb36
-rwxr-xr-xscripts/generate-memory-metrics-on-boot11
-rw-r--r--scripts/prepare_build.sh10
-rw-r--r--spec/controllers/groups_controller_spec.rb7
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb94
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb44
-rw-r--r--spec/controllers/projects/merge_requests/content_controller_spec.rb60
-rw-r--r--spec/controllers/projects/services_controller_spec.rb10
-rw-r--r--spec/factories/award_emoji.rb2
-rw-r--r--spec/factories/deployments.rb4
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb4
-rw-r--r--spec/features/projects/files/user_reads_pipeline_status_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_jira_spec.rb28
-rw-r--r--spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb4
-rw-r--r--spec/finders/branches_finder_spec.rb9
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json3
-rw-r--r--spec/frontend/branches/divergence_graph_spec.js32
-rw-r--r--spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap16
-rw-r--r--spec/frontend/repository/components/last_commit_spec.js21
-rw-r--r--spec/frontend/test_setup.js13
-rw-r--r--spec/graphql/gitlab_schema_spec.rb2
-rw-r--r--spec/graphql/types/award_emojis/award_emoji_type_spec.rb11
-rw-r--r--spec/graphql/types/commit_type_spec.rb11
-rw-r--r--spec/graphql/types/tree/tree_type_spec.rb2
-rw-r--r--spec/helpers/markup_helper_spec.rb37
-rw-r--r--spec/helpers/onboarding_experiment_helper_spec.rb38
-rw-r--r--spec/initializers/6_validations_spec.rb20
-rw-r--r--spec/javascripts/boards/components/board_spec.js61
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js63
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js2
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js20
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js8
-rw-r--r--spec/javascripts/registry/components/collapsible_container_spec.js18
-rw-r--r--spec/javascripts/registry/components/table_registry_spec.js19
-rw-r--r--spec/javascripts/test_bundle.js4
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js3
-rw-r--r--spec/javascripts/vue_shared/components/file_row_spec.js13
-rw-r--r--spec/lib/gitlab/auth/ip_rate_limiter_spec.rb65
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb22
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb66
-rw-r--r--spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb68
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb18
-rw-r--r--spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/copy_field_description_spec.rb21
-rw-r--r--spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb44
-rw-r--r--spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb20
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/project.json2
-rw-r--r--spec/lib/gitlab/issuable_sorter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb4
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb6
-rw-r--r--spec/mailers/notify_spec.rb14
-rw-r--r--spec/models/ci/pipeline_spec.rb11
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb2
-rw-r--r--spec/models/concerns/deployable_spec.rb14
-rw-r--r--spec/models/concerns/mentionable_spec.rb2
-rw-r--r--spec/models/deployment_spec.rb62
-rw-r--r--spec/models/project_services/jira_service_spec.rb12
-rw-r--r--spec/models/repository_spec.rb42
-rw-r--r--spec/policies/award_emoji_policy_spec.rb54
-rw-r--r--spec/presenters/award_emoji_presenter_spec.rb36
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb100
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb80
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb142
-rw-r--r--spec/requests/api/graphql/project/tree/tree_spec.rb18
-rw-r--r--spec/services/branches/diverging_commit_counts_service_spec.rb52
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb205
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb6
-rw-r--r--spec/services/system_note_service_spec.rb34
-rw-r--r--spec/support/api/boards_shared_examples.rb10
-rw-r--r--spec/support/helpers/email_helpers.rb15
-rw-r--r--spec/support/helpers/graphql_helpers.rb5
-rw-r--r--spec/support/helpers/jira_service_helper.rb2
-rw-r--r--spec/support/helpers/stub_configuration.rb5
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/graphql/mutation_shared_examples.rb34
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service.rb62
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb29
-rw-r--r--spec/tasks/migrate/schema_check_rake_spec.rb48
-rw-r--r--spec/views/projects/services/_form.haml_spec.rb6
-rw-r--r--yarn.lock212
411 files changed, 5675 insertions, 1849 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 0ca6d7a350a..f65e62068d6 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -19,3 +19,5 @@ db/ @abrandl @NikolayS
/lib/gitlab/ci/templates/ @nolith @zj
/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah
/lib/gitlab/ci/templates/Security/ @plafoucriere @gonzoyumo @twoodham
+/ee/app/models/project_alias.rb @patrickbajao
+/ee/lib/api/project_aliases.rb @patrickbajao
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index beab406fab7..d7f8d70699b 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -12,7 +12,9 @@
# Trigger a manual docs build in gitlab-docs only on non docs-only branches.
# Useful to preview the docs changes live.
review-docs-deploy-manual:
- <<: *review-docs
+ extends:
+ - .review-docs
+ - .no-docs-and-no-qa
stage: build
script:
- gem install gitlab --no-document
@@ -21,9 +23,6 @@ review-docs-deploy-manual:
only:
- branches@gitlab-org/gitlab-ce
- branches@gitlab-org/gitlab-ee
- except:
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
# Always trigger a docs build in gitlab-docs only on docs-only branches.
# Useful to preview the docs changes live.
@@ -69,7 +68,7 @@ docs lint:
# Lint Markdown
# https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md
- bundle exec mdl content/$DOCS_GITLAB_REPO_SUFFIX/**/*.md --rules \
- MD032
+ MD004,MD032
# Build HTML from Markdown
- bundle exec nanoc
# Check the internal links
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index cd6953a6ac2..8314c067d7a 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -70,9 +70,10 @@ gitlab:assets:compile pull-cache:
cache:
policy: pull
except:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - /(^docs[\/-].*|.*-docs$)/
+ refs:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - /(^docs[\/-].*|.*-docs$)/
.compile-assets-metadata:
extends: .dedicated-runner
@@ -107,9 +108,10 @@ compile-assets pull-cache:
cache:
policy: pull
except:
- - master@gitlab-org/gitlab-ce
- - master@gitlab-org/gitlab-ee
- - /(^docs[\/-].*|.*-docs$)/
+ refs:
+ - master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
+ - /(^docs[\/-].*|.*-docs$)/
gitlab:ui:visual:
extends: .dedicated-runner
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index eb50f08c1a7..4da7f404767 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -30,7 +30,14 @@
.no-docs:
except:
- - /(^docs[\/-].*|.*-docs$)/
+ refs:
+ - /(^docs[\/-].*|.*-docs$)/
+
+.no-docs-and-no-qa:
+ except:
+ refs:
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
.dedicated-no-docs-pull-cache-job:
extends:
@@ -38,10 +45,9 @@
- .no-docs
.dedicated-no-docs-and-no-qa-pull-cache-job:
- extends: .dedicated-pull-cache-job
- except:
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
+ extends:
+ - .dedicated-pull-cache-job
+ - .no-docs-and-no-qa
# Jobs that do not need a DB
.dedicated-no-docs-no-db-pull-cache-job:
diff --git a/.gitlab/ci/memory.gitlab-ci.yml b/.gitlab/ci/memory.gitlab-ci.yml
index 2f3907a331a..ffe5dbdc31b 100644
--- a/.gitlab/ci/memory.gitlab-ci.yml
+++ b/.gitlab/ci/memory.gitlab-ci.yml
@@ -17,3 +17,26 @@ memory-static:
- tmp/memory_*.txt
reports:
metrics: tmp/memory_metrics.txt
+
+# Show memory usage caused by invoking require per gem.
+# Unlike `memory-static`, it hits the app with one request to ensure that any last minute require-s have been called.
+# The application is booted in `production` environment.
+# All tests are run without a webserver (directly using Rack::Mock by default).
+memory-on-boot:
+ extends: .rspec-metadata-pg-10
+ variables:
+ NODE_ENV: "production"
+ RAILS_ENV: "production"
+ SETUP_DB: "true"
+ SKIP_STORAGE_VALIDATION: "true"
+ # we override the max_old_space_size to prevent OOM errors
+ NODE_OPTIONS: --max_old_space_size=3584
+ script:
+ # Both bootsnap and derailed monkey-patch Kernel#require, which leads to circular dependency
+ - DISABLE_BOOTSNAP=true PATH_TO_HIT="/users/sign_in" CUT_OFF=0.3 bundle exec derailed exec perf:mem >> 'tmp/memory_on_boot.txt'
+ - scripts/generate-memory-metrics-on-boot tmp/memory_on_boot.txt >> 'tmp/memory_on_boot_metrics.txt'
+ artifacts:
+ paths:
+ - tmp/memory_*.txt
+ reports:
+ metrics: tmp/memory_on_boot_metrics.txt
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index 6551f47d3be..009c18310e4 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -20,8 +20,9 @@
- master@gitlab/gitlab-ee
.gitlab-setup: &gitlab-setup
- extends: .dedicated-no-docs-and-no-qa-pull-cache-job
- <<: *use-pg
+ extends:
+ - .dedicated-no-docs-and-no-qa-pull-cache-job
+ - .use-pg
variables:
SETUP_DB: "false"
script:
@@ -43,7 +44,9 @@
- bundle exec rake $CI_JOB_NAME
.rspec-metadata: &rspec-metadata
- extends: .dedicated-pull-cache-job
+ extends:
+ - .dedicated-pull-cache-job
+ - .no-docs-and-no-qa
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
@@ -76,9 +79,6 @@
- tmp/capybara/
reports:
junit: junit_rspec.xml
- except:
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
.rspec-metadata-pg: &rspec-metadata-pg
<<: *rspec-metadata
@@ -122,8 +122,10 @@
- setup-test-env
setup-test-env:
- extends: .dedicated-runner-default-cache
- <<: *use-pg
+ extends:
+ - .dedicated-runner-default-cache
+ - .no-docs
+ - .use-pg
stage: prepare
script:
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
@@ -134,8 +136,6 @@ setup-test-env:
- tmp/tests
- config/secrets.yml
- vendor/gitaly-ruby
- except:
- - /(^docs[\/-].*|.*-docs$)/
rspec unit pg:
<<: *rspec-metadata-pg
@@ -200,11 +200,12 @@ static-analysis:
downtime_check:
<<: *rake-exec
except:
- - master
- - tags
- - /^[\d-]+-stable(-ee)?$/
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
+ refs:
+ - master
+ - tags
+ - /^[\d-]+-stable(-ee)?$/
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
dependencies:
- setup-test-env
@@ -212,12 +213,13 @@ ee_compat_check:
<<: *rake-exec
dependencies: []
except:
- - master
- - tags
- - /[\d-]+-stable(-ee)?/
- - /^security-/
- - branches@gitlab-org/gitlab-ee
- - branches@gitlab/gitlab-ee
+ refs:
+ - master
+ - tags
+ - /[\d-]+-stable(-ee)?/
+ - /^security-/
+ - branches@gitlab-org/gitlab-ee
+ - branches@gitlab/gitlab-ee
retry: 0
artifacts:
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
@@ -244,7 +246,7 @@ migration:path-pg:
extends: .dedicated-no-docs-and-no-qa-pull-cache-job
script:
- bundle exec rake db:migrate VERSION=20170523121229
- - bundle exec rake db:migrate
+ - bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true
dependencies:
- setup-test-env
@@ -261,7 +263,9 @@ gitlab:setup-pg:
coverage:
# Don't include dedicated-no-docs-no-db-pull-cache-job here since we need to
# download artifacts from all the rspec jobs instead of from setup-test-env only
- extends: .dedicated-runner-default-cache
+ extends:
+ - .dedicated-runner-default-cache
+ - .no-docs-and-no-qa
cache:
policy: pull
variables:
@@ -276,6 +280,3 @@ coverage:
paths:
- coverage/index.html
- coverage/assets/
- except:
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml
index debc90a1cb0..c1fc3a893ca 100644
--- a/.gitlab/ci/setup.gitlab-ci.yml
+++ b/.gitlab/ci/setup.gitlab-ci.yml
@@ -15,7 +15,9 @@ cache gems:
- setup-test-env
gitlab_git_test:
- extends: .dedicated-runner
+ extends:
+ - .dedicated-runner
+ - .no-docs-and-no-qa
variables:
SETUP_DB: "false"
before_script: []
@@ -23,12 +25,11 @@ gitlab_git_test:
cache: {}
script:
- spec/support/prepare-gitlab-git-test-for-commit --check-for-changes
- except:
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
no_ee_check:
- extends: .dedicated-runner
+ extends:
+ - .dedicated-runner
+ - .no-docs-and-no-qa
variables:
SETUP_DB: "false"
before_script: []
@@ -38,6 +39,3 @@ no_ee_check:
- scripts/no-ee-check
only:
- /.+/@gitlab-org/gitlab-ce
- except:
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
index c51f825f831..2454ea85652 100644
--- a/.gitlab/ci/test-metadata.gitlab-ci.yml
+++ b/.gitlab/ci/test-metadata.gitlab-ci.yml
@@ -12,7 +12,9 @@
- rspec_profiling/
retrieve-tests-metadata:
- <<: *tests-metadata-state
+ extends:
+ - .tests-metadata-state
+ - .no-docs-and-no-qa
stage: prepare
cache:
key: tests_metadata
@@ -25,9 +27,6 @@ retrieve-tests-metadata:
- mkdir -p rspec_profiling/
- wget -O $FLAKY_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$FLAKY_RSPEC_SUITE_REPORT_PATH || rm $FLAKY_RSPEC_SUITE_REPORT_PATH
- '[[ -f $FLAKY_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_SUITE_REPORT_PATH}'
- except:
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
update-tests-metadata:
<<: *tests-metadata-state
@@ -69,9 +68,10 @@ flaky-examples-check:
only:
- branches
except:
- - master
- - /(^docs[\/-].*|.*-docs$)/
- - /(^qa[\/-].*|.*-qa$)/
+ refs:
+ - master
+ - /(^docs[\/-].*|.*-docs$)/
+ - /(^qa[\/-].*|.*-qa$)/
artifacts:
expire_in: 30d
paths:
diff --git a/Gemfile b/Gemfile
index 302f39756e8..1264d75eac6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,7 +11,7 @@ gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.7.0'
# Default values for AR models
-gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for'
+gem 'default_value_for', '~> 3.2.0'
# Supported DBs
gem 'mysql2', '~> 0.4.10', group: :mysql
@@ -211,7 +211,7 @@ gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
# HipChat integration
gem 'hipchat', '~> 1.5.0'
-# JIRA integration
+# Jira integration
gem 'jira-ruby', '~> 1.4'
# Flowdock integration
@@ -309,7 +309,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false
# Prometheus
- gem 'prometheus-client-mmap', '~> 0.9.4'
+ gem 'prometheus-client-mmap', '~> 0.9.6'
gem 'raindrops', '~> 0.18'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 4881faac0e0..5b648d43137 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -163,6 +163,8 @@ GEM
html-pipeline
declarative (0.0.10)
declarative-option (0.1.0)
+ default_value_for (3.2.0)
+ activerecord (>= 3.2.0, < 6.0)
derailed_benchmarks (1.3.5)
benchmark-ips (~> 2)
get_process_mem (~> 0)
@@ -304,8 +306,6 @@ GEM
gitaly-proto (1.32.0)
grpc (~> 1.0)
github-markup (1.7.0)
- gitlab-default_value_for (3.1.1)
- activerecord (>= 3.2.0, < 6.0)
gitlab-labkit (0.3.0)
actionpack (~> 5)
activesupport (~> 5)
@@ -652,7 +652,7 @@ GEM
parser
unparser
procto (0.0.3)
- prometheus-client-mmap (0.9.4)
+ prometheus-client-mmap (0.9.6)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@@ -1056,6 +1056,7 @@ DEPENDENCIES
creole (~> 0.5.0)
database_cleaner (~> 1.7.0)
deckar01-task_list (= 2.2.0)
+ default_value_for (~> 3.2.0)
derailed_benchmarks
device_detector
devise (~> 4.6)
@@ -1093,7 +1094,6 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 1.32.0)
github-markup (~> 1.7.0)
- gitlab-default_value_for (~> 3.1.1)
gitlab-labkit (~> 0.3.0)
gitlab-markup (~> 1.7.0)
gitlab-sidekiq-fetcher (~> 0.4.0)
@@ -1173,7 +1173,7 @@ DEPENDENCIES
peek-redis (~> 1.2.0)
pg (~> 1.1)
premailer-rails (~> 1.9.7)
- prometheus-client-mmap (~> 0.9.4)
+ prometheus-client-mmap (~> 0.9.6)
pry-byebug (~> 3.5.1)
pry-rails (~> 0.3.4)
puma (~> 3.12)
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 45b9e57f9ab..c6122fbc686 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -1,6 +1,7 @@
+import $ from 'jquery';
import Sortable from 'sortablejs';
import Vue from 'vue';
-import { n__ } from '~/locale';
+import { n__, s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import AccessorUtilities from '../../lib/utils/accessor';
@@ -53,12 +54,19 @@ export default Vue.extend({
const { issuesSize } = this.list;
return `${n__('%d issue', '%d issues', issuesSize)}`;
},
+ caretTooltip() {
+ return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
+ },
isNewIssueShown() {
return (
this.list.type === 'backlog' ||
(!this.disabled && this.list.type !== 'closed' && this.list.type !== 'blank')
);
},
+ uniqueKey() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
+ },
},
watch: {
filter: {
@@ -72,31 +80,34 @@ export default Vue.extend({
},
},
mounted() {
- this.sortableOptions = getBoardSortableDefaultOptions({
+ const instance = this;
+
+ const sortableOptions = getBoardSortableDefaultOptions({
disabled: this.disabled,
group: 'boards',
draggable: '.is-draggable',
handle: '.js-board-handle',
- onEnd: e => {
+ onEnd(e) {
sortableEnd();
+ const sortable = this;
+
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
- const order = this.sortable.toArray();
+ const order = sortable.toArray();
const list = boardsStore.findList('id', parseInt(e.item.dataset.id, 10));
- this.$nextTick(() => {
+ instance.$nextTick(() => {
boardsStore.moveList(list, order);
});
}
},
});
- this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
+ Sortable.create(this.$el.parentNode, sortableOptions);
},
created() {
if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) {
- const isCollapsed =
- localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false';
+ const isCollapsed = localStorage.getItem(`${this.uniqueKey}.expanded`) === 'false';
this.list.isExpanded = !isCollapsed;
}
@@ -105,16 +116,17 @@ export default Vue.extend({
showNewIssueForm() {
this.$refs['board-list'].showIssueForm = !this.$refs['board-list'].showIssueForm;
},
- toggleExpanded(e) {
- if (this.list.isExpandable && !e.target.classList.contains('js-no-trigger-collapse')) {
+ toggleExpanded() {
+ if (this.list.isExpandable) {
this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
- localStorage.setItem(
- `boards.${this.boardId}.${this.list.type}.expanded`,
- this.list.isExpanded,
- );
+ localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
}
+
+ // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
+ // Close all tooltips manually to prevent dangling tooltips.
+ $('.tooltip').tooltip('hide');
}
},
},
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index 1cbd31729cd..f58149c9f7b 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -1,4 +1,5 @@
<script>
+import { __ } from '~/locale';
/* global ListLabel */
import Cookies from 'js-cookie';
import boardsStore from '../stores/boards_store';
@@ -7,8 +8,8 @@ export default {
data() {
return {
predefinedLabels: [
- new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
- new ListLabel({ title: 'Doing', color: '#5CB85C' }),
+ new ListLabel({ title: __('To Do'), color: '#F0AD4E' }),
+ new ListLabel({ title: __('Doing'), color: '#5CB85C' }),
],
};
},
@@ -58,7 +59,11 @@ export default {
<template>
<div class="board-blank-state p-3">
- <p>Add the following default lists to your Issue Board with one click:</p>
+ <p>
+ {{
+ __('BoardBlankState|Add the following default lists to your Issue Board with one click:')
+ }}
+ </p>
<ul class="list-unstyled board-blank-state-list">
<li v-for="(label, index) in predefinedLabels" :key="index">
<span
@@ -70,18 +75,21 @@ export default {
</li>
</ul>
<p>
- Starting out with the default set of lists will get you right on the way to making the most of
- your board.
+ {{
+ __(
+ 'BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board.',
+ )
+ }}
</p>
<button
class="btn btn-success btn-inverted btn-block"
type="button"
@click.stop="addDefaultLists"
>
- Add default lists
+ {{ __('BoardBlankState|Add default lists') }}
</button>
<button class="btn btn-default btn-block" type="button" @click.stop="clearBlankState">
- Nevermind, I'll use my own
+ {{ __("BoardBlankState|Nevermind, I'll use my own") }}
</button>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index b1a8b13f3ac..787ff110bf8 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -227,7 +227,7 @@ export default {
:class="{ 'd-none': !list.isExpanded, 'd-flex flex-column': list.isExpanded }"
class="board-list-component position-relative h-100"
>
- <div v-if="loading" class="board-list-loading text-center" aria-label="Loading issues">
+ <div v-if="loading" class="board-list-loading text-center" :aria-label="__('Loading issues')">
<gl-loading-icon />
</div>
<board-new-issue
@@ -257,7 +257,7 @@ export default {
/>
<li v-if="showCount" class="board-list-count text-center" data-issue-id="-1">
<gl-loading-icon v-show="list.loadingMore" label="Loading more issues" />
- <span v-if="list.issues.length === list.issuesSize"> Showing all issues </span>
+ <span v-if="list.issues.length === list.issuesSize">{{ __('Showing all issues') }}</span>
<span v-else> Showing {{ list.issues.length }} of {{ list.issuesSize }} issues </span>
</li>
</ul>
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index cc6af8e88cd..4180023b7db 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -102,9 +102,9 @@ export default {
<div class="board-card position-relative p-3 rounded">
<form @submit="submit($event)">
<div v-if="error" class="flash-container">
- <div class="flash-alert">An error occurred. Please try again.</div>
+ <div class="flash-alert">{{ __('An error occurred. Please try again.') }}</div>
</div>
- <label :for="list.id + '-title'" class="label-bold"> Title </label>
+ <label :for="list.id + '-title'" class="label-bold">{{ __('Title') }}</label>
<input
:id="list.id + '-title'"
ref="input"
@@ -122,12 +122,11 @@ export default {
class="float-left"
variant="success"
type="submit"
+ >{{ __('Submit issue') }}</gl-button
>
- Submit issue
- </gl-button>
- <gl-button class="float-right" type="button" variant="default" @click="cancel">
- Cancel
- </gl-button>
+ <gl-button class="float-right" type="button" variant="default" @click="cancel">{{
+ __('Cancel')
+ }}</gl-button>
</div>
</form>
</div>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index a8516f178fc..7f554c99669 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -124,7 +124,7 @@ export default {
return `${this.rootPath}${assignee.username}`;
},
avatarUrlTitle(assignee) {
- return `Avatar for ${assignee.name}`;
+ return sprintf(__(`Avatar for %{assigneeName}`), { assigneeName: assignee.name });
},
showLabel(label) {
if (!label.id) return false;
@@ -160,9 +160,10 @@ export default {
:title="__('Confidential')"
class="confidential-icon append-right-4"
:aria-label="__('Confidential')"
- /><a :href="issue.path" :title="issue.title" class="js-no-trigger" @mousemove.stop>{{
- issue.title
- }}</a>
+ />
+ <a :href="issue.path" :title="issue.title" class="js-no-trigger" @mousemove.stop>
+ {{ issue.title }}
+ </a>
</h4>
</div>
<div v-if="showLabelFooter" class="board-card-labels prepend-top-4 d-flex flex-wrap">
@@ -204,13 +205,13 @@ export default {
placement="bottom"
class="board-issue-path block-truncated bold"
>{{ issueReferencePath }}</tooltip-on-truncate
- >#{{ issue.iid }}
+ >
+ #{{ issue.iid }}
</span>
<span class="board-info-items prepend-top-8 d-inline-block">
- <issue-due-date v-if="issue.dueDate" :date="issue.dueDate" /><issue-time-estimate
- v-if="issue.timeEstimate"
- :estimate="issue.timeEstimate"
- /><issue-card-weight
+ <issue-due-date v-if="issue.dueDate" :date="issue.dueDate" />
+ <issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
+ <issue-card-weight
v-if="issue.weight"
:weight="issue.weight"
@click="filterByWeight(issue.weight)"
@@ -230,7 +231,8 @@ export default {
tooltip-placement="bottom"
>
<span class="js-assignee-tooltip">
- <span class="bold d-block">Assignee</span> {{ assignee.name }}
+ <span class="bold d-block">{{ __('Assignee') }}</span>
+ {{ assignee.name }}
<span class="text-white-50">@{{ assignee.username }}</span>
</span>
</user-avatar-link>
@@ -240,9 +242,8 @@ export default {
:title="assigneeCounterTooltip"
class="avatar-counter"
data-placement="bottom"
+ >{{ assigneeCounterLabel }}</span
>
- {{ assigneeCounterLabel }}
- </span>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index 091700de93f..66f59009714 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -1,4 +1,5 @@
<script>
+import { __, sprintf } from '~/locale';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
@@ -20,19 +21,20 @@ export default {
computed: {
contents() {
const obj = {
- title: "You haven't added any issues to your project yet",
- content: `
- An issue can be a bug, a todo or a feature request that needs to be
- discussed in a project. Besides, issues are searchable and filterable.
- `,
+ title: __("You haven't added any issues to your project yet"),
+ content: __(
+ 'An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable.',
+ ),
};
if (this.activeTab === 'selected') {
- obj.title = "You haven't selected any issues yet";
- obj.content = `
- Go back to <strong>Open issues</strong> and select some issues
- to add to your board.
- `;
+ obj.title = __("You haven't selected any issues yet");
+ obj.content = sprintf(
+ __(
+ 'Go back to %{startTag}Open issues%{endTag} and select some issues to add to your board.',
+ ),
+ { startTag: '<strong>', endTag: '</strong>' },
+ );
}
return obj;
@@ -51,16 +53,16 @@ export default {
<div class="text-content">
<h4>{{ contents.title }}</h4>
<p v-html="contents.content"></p>
- <a v-if="activeTab === 'all'" :href="newIssuePath" class="btn btn-success btn-inverted">
- New issue
- </a>
+ <a v-if="activeTab === 'all'" :href="newIssuePath" class="btn btn-success btn-inverted">{{
+ __('New issue')
+ }}</a>
<button
v-if="activeTab === 'selected'"
class="btn btn-default"
type="button"
@click="changeTab('all')"
>
- Open issues
+ {{ __('Open issues') }}
</button>
</div>
</div>
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index d4afd9d59da..a1d634c8f19 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -1,8 +1,7 @@
<script>
import Flash from '../../../flash';
-import { __ } from '../../../locale';
+import { __, n__ } from '../../../locale';
import ListsDropdown from './lists_dropdown.vue';
-import { pluralize } from '../../../lib/utils/text_utility';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
import boardsStore from '../../stores/boards_store';
@@ -24,8 +23,8 @@ export default {
},
submitText() {
const count = ModalStore.selectedCount();
-
- return `Add ${count > 0 ? count : ''} ${pluralize('issue', count)}`;
+ if (!count) return __('Add issues');
+ return n__(`Add %d issue`, `Add %d issues`, count);
},
},
methods: {
@@ -68,11 +67,11 @@ export default {
<button :disabled="submitDisabled" class="btn btn-success" type="button" @click="addIssues">
{{ submitText }}
</button>
- <span class="inline add-issues-footer-to-list"> to list </span>
+ <span class="inline add-issues-footer-to-list">{{ __('to list') }}</span>
<lists-dropdown />
</div>
<button class="btn btn-default float-right" type="button" @click="toggleModal(false)">
- Cancel
+ {{ __('Cancel') }}
</button>
</footer>
</template>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index 1cfa6d39362..7a696035dc8 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -1,4 +1,5 @@
<script>
+import { __ } from '~/locale';
import ModalFilters from './filters';
import ModalTabs from './tabs.vue';
import ModalStore from '../../stores/modal_store';
@@ -30,10 +31,10 @@ export default {
computed: {
selectAllText() {
if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
- return 'Select all';
+ return __('Select all');
}
- return 'Deselect all';
+ return __('Deselect all');
},
showSearch() {
return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
@@ -57,7 +58,7 @@ export default {
type="button"
class="close"
data-dismiss="modal"
- aria-label="Close"
+ :aria-label="__('Close')"
@click="toggleModal(false)"
>
<span aria-hidden="true">×</span>
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index 28d2019af2f..1802b543687 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -123,7 +123,9 @@ export default {
class="empty-state add-issues-empty-state-filter text-center"
>
<div class="svg-content"><img :src="emptyStateSvg" /></div>
- <div class="text-content"><h4>There are no issues to show.</h4></div>
+ <div class="text-content">
+ <h4>{{ __('There are no issues to show.') }}</h4>
+ </div>
</div>
<div v-for="(group, index) in groupedIssues" :key="index" class="add-issues-list-column">
<div v-for="issue in group" v-if="showIssue(issue)" :key="issue.id" class="board-card-parent">
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 8274647744f..a1cf1866faf 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -1,4 +1,5 @@
<script>
+import { __ } from '~/locale';
import $ from 'jquery';
import _ from 'underscore';
import Icon from '~/vue_shared/components/icon.vue';
@@ -27,7 +28,7 @@ export default {
},
computed: {
selectedProjectName() {
- return this.selectedProject.name || 'Select a project';
+ return this.selectedProject.name || __('Select a project');
},
},
mounted() {
@@ -81,7 +82,7 @@ export default {
<template>
<div>
- <label class="label-bold prepend-top-10"> Project </label>
+ <label class="label-bold prepend-top-10">{{ __('Project') }}</label>
<div ref="projectsDropdown" class="dropdown dropdown-projects">
<button
class="dropdown-menu-toggle wide"
@@ -92,9 +93,9 @@ export default {
{{ selectedProjectName }} <icon name="chevron-down" />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
- <div class="dropdown-title">Projects</div>
+ <div class="dropdown-title">{{ __('Projects') }}</div>
<div class="dropdown-input">
- <input class="dropdown-input-field" type="search" placeholder="Search projects" />
+ <input class="dropdown-input-field" type="search" :placeholder="__('Search projects')" />
<icon name="search" class="dropdown-input-search" data-hidden="true" />
</div>
<div class="dropdown-content"></div>
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
index 4ab2b17301f..b84722244d1 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
@@ -76,7 +76,7 @@ export default Vue.extend({
<template>
<div class="block list">
<button class="btn btn-default btn-block" type="button" @click="removeIssue">
- Remove from board
+ {{ __('Remove from board') }}
</button>
</div>
</template>
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index 636ca99952c..68ea28e68d9 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
@@ -20,7 +20,7 @@ export function getBoardSortableDefaultOptions(obj) {
'ontouchstart' in window || (window.DocumentTouch && document instanceof DocumentTouch);
const defaultSortOptions = Object.assign({}, sortableConfig, {
- filter: '.board-delete, .btn',
+ filter: '.no-drag',
delay: touchEnabled ? 100 : 0,
scrollSensitivity: touchEnabled ? 60 : 100,
scrollSpeed: 20,
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index a9d88f19146..cd553d0c4af 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -26,6 +26,12 @@ const TYPES = {
isExpandable: false,
isBlank: true,
},
+ default: {
+ // includes label, assignee, and milestone lists
+ isPreset: false,
+ isExpandable: true,
+ isBlank: false,
+ },
};
class List {
@@ -249,7 +255,7 @@ class List {
}
getTypeInfo(type) {
- return TYPES[type] || {};
+ return TYPES[type] || TYPES.default;
}
onNewIssueResponse(issue, data) {
diff --git a/app/assets/javascripts/branches/divergence_graph.js b/app/assets/javascripts/branches/divergence_graph.js
index 670e8e9eb60..96bc6a5f8e8 100644
--- a/app/assets/javascripts/branches/divergence_graph.js
+++ b/app/assets/javascripts/branches/divergence_graph.js
@@ -1,23 +1,49 @@
import Vue from 'vue';
+import { __ } from '../locale';
+import createFlash from '../flash';
+import axios from '../lib/utils/axios_utils';
import DivergenceGraph from './components/divergence_graph.vue';
-export default () => {
- document.querySelectorAll('.js-branch-divergence-graph').forEach(el => {
- const { distance, aheadCount, behindCount, defaultBranch, maxCommits } = el.dataset;
-
- return new Vue({
- el,
- render(h) {
- return h(DivergenceGraph, {
- props: {
- defaultBranch,
- distance: distance ? parseInt(distance, 10) : null,
- aheadCount: parseInt(aheadCount, 10),
- behindCount: parseInt(behindCount, 10),
- maxCommits: parseInt(maxCommits, 10),
- },
- });
- },
- });
+export function createGraphVueApp(el, data, maxCommits) {
+ return new Vue({
+ el,
+ render(h) {
+ return h(DivergenceGraph, {
+ props: {
+ defaultBranch: 'master',
+ distance: data.distance ? parseInt(data.distance, 10) : null,
+ aheadCount: parseInt(data.ahead, 10),
+ behindCount: parseInt(data.behind, 10),
+ maxCommits,
+ },
+ });
+ },
});
+}
+
+export default endpoint => {
+ const names = [...document.querySelectorAll('.js-branch-item')].map(
+ ({ dataset }) => dataset.name,
+ );
+ return axios
+ .get(endpoint, {
+ params: { names },
+ })
+ .then(({ data }) => {
+ const maxCommits = Object.entries(data).reduce((acc, [, val]) => {
+ const max = Math.max(...Object.values(val));
+ return max > acc ? max : acc;
+ }, 100);
+
+ Object.entries(data).forEach(([branchName, val]) => {
+ const el = document.querySelector(`.js-branch-${branchName} .js-branch-divergence-graph`);
+
+ if (!el) return;
+
+ createGraphVueApp(el, val, maxCommits);
+ });
+ })
+ .catch(() =>
+ createFlash(__('Error fetching diverging counts for branches. Please try again.')),
+ );
};
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 4771090aa7e..cd2121db3b2 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -207,7 +207,7 @@ export default {
return __('Updating');
}
- return __('Updated');
+ return this.updateSuccessful ? __('Updated to') : __('Updated');
},
updateFailureDescription() {
return s__('ClusterIntegration|Update failed. Please check the logs and try again.');
@@ -331,8 +331,6 @@ export default {
class="form-text text-muted label p-0 js-cluster-application-update-details"
>
{{ versionLabel }}
- <span v-if="updateSuccessful">to</span>
-
<gl-link
v-if="updateSuccessful"
:href="chartRepo"
diff --git a/app/assets/javascripts/clusters/components/knative_domain_editor.vue b/app/assets/javascripts/clusters/components/knative_domain_editor.vue
index 480228619a5..e26ef135bc5 100644
--- a/app/assets/javascripts/clusters/components/knative_domain_editor.vue
+++ b/app/assets/javascripts/clusters/components/knative_domain_editor.vue
@@ -2,7 +2,7 @@
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
import { GlLoadingIcon } from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { __, s__ } from '~/locale';
import { APPLICATION_STATUS } from '~/clusters/constants';
@@ -32,7 +32,7 @@ export default {
return [UPDATING].includes(this.knative.status);
},
saveButtonLabel() {
- return this.saving ? this.__('Saving') : this.__('Save changes');
+ return this.saving ? __('Saving') : __('Save changes');
},
knativeInstalled() {
return this.knative.installed;
@@ -122,9 +122,9 @@ export default {
`ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint.`,
)
}}
- <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">
- {{ __('More information') }}
- </a>
+ <a :href="ingressDnsHelpPath" target="_blank" rel="noopener noreferrer">{{
+ __('More information')
+ }}</a>
</p>
<p
diff --git a/app/assets/javascripts/clusters/components/uninstall_application_button.vue b/app/assets/javascripts/clusters/components/uninstall_application_button.vue
index ef4bcbe14dd..8465312d84d 100644
--- a/app/assets/javascripts/clusters/components/uninstall_application_button.vue
+++ b/app/assets/javascripts/clusters/components/uninstall_application_button.vue
@@ -1,6 +1,7 @@
<script>
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { APPLICATION_STATUS } from '~/clusters/constants';
+import { __ } from '~/locale';
const { UPDATING, UNINSTALLING } = APPLICATION_STATUS;
@@ -22,7 +23,7 @@ export default {
return this.status === UNINSTALLING;
},
label() {
- return this.loading ? this.__('Uninstalling') : this.__('Uninstall');
+ return this.loading ? __('Uninstalling') : __('Uninstall');
},
},
};
diff --git a/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue b/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
index 65827f1cb6a..920439ebb23 100644
--- a/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
+++ b/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
@@ -14,7 +14,9 @@ const CUSTOM_APP_WARNING_TEXT = {
[PROMETHEUS]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
[RUNNER]: s__('ClusterIntegration|Any running pipelines will be canceled.'),
[KNATIVE]: s__('ClusterIntegration|The associated IP will be deleted and cannot be restored.'),
- [JUPYTER]: '',
+ [JUPYTER]: s__(
+ 'ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored.',
+ ),
};
export default {
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index 208bd19f6b0..21244c14977 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -1,5 +1,5 @@
<script>
-import { s__, sprintf } from '~/locale';
+import { __, s__, sprintf } from '~/locale';
import { formatTime } from '~/lib/utils/datetime_utility';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
@@ -28,7 +28,7 @@ export default {
},
computed: {
title() {
- return 'Deploy to...';
+ return __('Deploy to...');
},
},
methods: {
@@ -80,7 +80,8 @@ export default {
data-toggle="dropdown"
>
<span>
- <icon name="play" /> <icon name="chevron-down" />
+ <icon name="play" />
+ <icon name="chevron-down" />
<gl-loading-icon v-if="isLoading" />
</span>
</button>
@@ -94,9 +95,10 @@ export default {
class="js-manual-action-link no-btn btn d-flex align-items-center"
@click="onClickAction(action)"
>
- <span class="flex-fill"> {{ action.name }} </span>
+ <span class="flex-fill">{{ action.name }}</span>
<span v-if="action.scheduledAt" class="text-secondary">
- <icon name="clock" /> {{ remainingTime(action) }}
+ <icon name="clock" />
+ {{ remainingTime(action) }}
</span>
</button>
</li>
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index f0e80cba753..dc68443493c 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -1,4 +1,5 @@
<script>
+import { __, sprintf } from '~/locale';
import Timeago from 'timeago.js';
import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui';
@@ -172,7 +173,9 @@ export default {
this.model.last_deployment.user &&
this.model.last_deployment.user.username
) {
- return `${this.model.last_deployment.user.username}'s avatar'`;
+ return sprintf(__("%{username}'s avatar"), {
+ username: this.model.last_deployment.user.username,
+ });
}
return '';
},
@@ -293,6 +296,9 @@ export default {
* @returns {Boolean|Undefined}
*/
isLastDeployment() {
+ // TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings
+ // name: 'last?' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
+ // Vue i18n ESLint rules issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/63560
return this.model && this.model.last_deployment && this.model.last_deployment['last?'];
},
diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue
index ae4f07a71cd..886490847ea 100644
--- a/app/assets/javascripts/environments/components/environment_monitoring.vue
+++ b/app/assets/javascripts/environments/components/environment_monitoring.vue
@@ -1,4 +1,5 @@
<script>
+import { __ } from '~/locale';
/**
* Renders the Monitoring (Metrics) link in environments table.
*/
@@ -21,7 +22,7 @@ export default {
},
computed: {
title() {
- return 'Monitoring';
+ return __('Monitoring');
},
},
};
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue
index 13195d32cc4..37f94f9f5ab 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.vue
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue
@@ -5,6 +5,7 @@
*/
import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
+import { __ } from '~/locale';
export default {
components: {
@@ -27,7 +28,7 @@ export default {
},
computed: {
title() {
- return 'Terminal';
+ return __('Terminal');
},
},
};
diff --git a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
index 060d8e25227..ef1d1e49320 100644
--- a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
+++ b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
@@ -49,9 +49,9 @@ export default {
</p>
</div>
<div class="form-group" :class="{ 'gl-show-field-errors': connectError }">
- <label class="label-bold" for="error-tracking-token">{{
- s__('ErrorTracking|Auth Token')
- }}</label>
+ <label class="label-bold" for="error-tracking-token">
+ {{ s__('ErrorTracking|Auth Token') }}
+ </label>
<div class="row">
<div class="col-8 col-md-9 gl-pr-0">
<gl-form-input
@@ -65,9 +65,8 @@ export default {
<gl-button
class="js-error-tracking-connect prepend-left-5"
@click="$emit('handle-connect')"
+ >{{ __('Connect') }}</gl-button
>
- {{ __('Connect') }}
- </gl-button>
<icon
v-show="connectSuccessful"
class="js-error-tracking-connect-success prepend-left-5 text-success align-middle"
diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
index 19bc3313373..4757c4b1e43 100644
--- a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
+++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
@@ -59,7 +59,7 @@ export default {
<template>
<div>
<div v-if="!isLocalStorageAvailable" class="dropdown-info-note">
- This feature requires local storage to be enabled
+ {{ __('This feature requires local storage to be enabled') }}
</div>
<ul v-else-if="hasItems">
<li v-for="(item, index) in processedItems" :key="`processed-items-${index}`">
@@ -90,10 +90,10 @@ export default {
class="filtered-search-history-clear-button"
@click="onRequestClearRecentSearches($event)"
>
- Clear recent searches
+ {{ __('Clear recent searches') }}
</button>
</li>
</ul>
- <div v-else class="dropdown-info-note">You don't have any recent searches</div>
+ <div v-else class="dropdown-info-note">{{ __("You don't have any recent searches") }}</div>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index b0c4969c5e4..f952b1e7b80 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -40,27 +40,36 @@ export default {
},
showContentViewer() {
return (
- (this.shouldHideEditor || this.file.viewMode === 'preview') &&
+ (this.shouldHideEditor || this.isPreviewViewMode) &&
(this.viewer !== viewerTypes.mr || !this.file.mrChange)
);
},
showDiffViewer() {
return this.shouldHideEditor && this.file.mrChange && this.viewer === viewerTypes.mr;
},
+ isEditorViewMode() {
+ return this.file.viewMode === 'editor';
+ },
+ isPreviewViewMode() {
+ return this.file.viewMode === 'preview';
+ },
editTabCSS() {
return {
- active: this.file.viewMode === 'editor',
+ active: this.isEditorViewMode,
};
},
previewTabCSS() {
return {
- active: this.file.viewMode === 'preview',
+ active: this.isPreviewViewMode,
};
},
fileType() {
const info = viewerInformationForPath(this.file.path);
return (info && info.id) || '';
},
+ showEditor() {
+ return !this.shouldHideEditor && this.isEditorViewMode;
+ },
},
watch: {
file(newVal, oldVal) {
@@ -89,7 +98,7 @@ export default {
}
},
rightPanelCollapsed() {
- this.editor.updateDimensions();
+ this.refreshEditorDimensions();
},
viewer() {
if (!this.file.pending) {
@@ -98,11 +107,17 @@ export default {
},
panelResizing() {
if (!this.panelResizing) {
- this.editor.updateDimensions();
+ this.refreshEditorDimensions();
}
},
rightPaneIsOpen() {
- this.editor.updateDimensions();
+ this.refreshEditorDimensions();
+ },
+ showEditor(val) {
+ if (val) {
+ // We need to wait for the editor to actually be rendered.
+ this.$nextTick(() => this.refreshEditorDimensions());
+ }
},
},
beforeDestroy() {
@@ -212,6 +227,11 @@ export default {
eol: this.model.eol,
});
},
+ refreshEditorDimensions() {
+ if (this.showEditor) {
+ this.editor.updateDimensions();
+ }
+ },
},
viewerTypes,
};
@@ -249,7 +269,7 @@ export default {
</div>
<file-templates-bar v-if="showFileTemplatesBar(file.name)" />
<div
- v-show="!shouldHideEditor && file.viewMode === 'editor'"
+ v-show="showEditor"
ref="editor"
:class="{
'is-readonly': isCommitModeActive,
diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js
index 5857f9e22ae..c05db4a5c71 100644
--- a/app/assets/javascripts/lib/graphql.js
+++ b/app/assets/javascripts/lib/graphql.js
@@ -22,7 +22,7 @@ export default (resolvers = {}, config = {}) => {
return new ApolloClient({
link: ApolloLink.split(
- operation => operation.getContext().hasUpload,
+ operation => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
createUploadLink(httpOptions),
new BatchHttpLink(httpOptions),
),
diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue
index 22cca756ef6..1357a5268d6 100644
--- a/app/assets/javascripts/notes/components/discussion_actions.vue
+++ b/app/assets/javascripts/notes/components/discussion_actions.vue
@@ -39,20 +39,27 @@ export default {
</script>
<template>
- <div class="discussion-with-resolve-btn">
+ <div class="discussion-with-resolve-btn clearfix">
<reply-placeholder class="qa-discussion-reply" @onClick="$emit('showReplyForm')" />
- <resolve-discussion-button
- v-if="discussion.resolvable"
- :is-resolving="isResolving"
- :button-title="resolveButtonTitle"
- @onClick="$emit('resolve')"
- />
- <div v-if="discussion.resolvable" class="btn-group discussion-actions ml-sm-2" role="group">
- <resolve-with-issue-button v-if="resolveWithIssuePath" :url="resolveWithIssuePath" />
- <jump-to-next-discussion-button
- v-if="shouldShowJumpToNextDiscussion"
- @onClick="$emit('jumpToNextDiscussion')"
+
+ <div class="btn-group discussion-actions" role="group">
+ <resolve-discussion-button
+ v-if="discussion.resolvable"
+ :is-resolving="isResolving"
+ :button-title="resolveButtonTitle"
+ @onClick="$emit('resolve')"
+ />
+ <resolve-with-issue-button
+ v-if="discussion.resolvable && resolveWithIssuePath"
+ :url="resolveWithIssuePath"
/>
</div>
+
+ <div
+ v-if="discussion.resolvable && shouldShowJumpToNextDiscussion"
+ class="btn-group discussion-actions ml-sm-2"
+ >
+ <jump-to-next-discussion-button @onClick="$emit('jumpToNextDiscussion')" />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 10b15a9c38c..b8eaff32cce 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -126,10 +126,7 @@ export default {
return this.discussion.resolved_by_push ? __('Automatically resolved') : __('Resolved');
},
shouldShowJumpToNextDiscussion() {
- return this.showJumpToNextDiscussion(
- this.discussion.id,
- this.discussionsByDiffOrder ? 'diff' : 'discussion',
- );
+ return this.showJumpToNextDiscussion(this.discussionsByDiffOrder ? 'diff' : 'discussion');
},
shouldRenderDiffs() {
return this.discussion.diff_discussion && this.renderDiffFile;
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index d7982be3e4b..8aa8f5037b3 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -61,15 +61,13 @@ export const unresolvedDiscussionsCount = state => state.unresolvedDiscussionsCo
export const resolvableDiscussionsCount = state => state.resolvableDiscussionsCount;
export const hasUnresolvedDiscussions = state => state.hasUnresolvedDiscussions;
-export const showJumpToNextDiscussion = (state, getters) => (discussionId, mode = 'discussion') => {
+export const showJumpToNextDiscussion = (state, getters) => (mode = 'discussion') => {
const orderedDiffs =
mode !== 'discussion'
? getters.unresolvedDiscussionsIdsByDiff
: getters.unresolvedDiscussionsIdsByDate;
- const indexOf = orderedDiffs.indexOf(discussionId);
-
- return indexOf !== -1 && indexOf < orderedDiffs.length - 1;
+ return orderedDiffs.length > 1;
};
export const isDiscussionResolved = (state, getters) => discussionId =>
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
index 29de3b7806c..37e8c75f299 100644
--- a/app/assets/javascripts/pages/projects/branches/index/index.js
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -5,5 +5,5 @@ import initDiverganceGraph from '~/branches/divergence_graph';
document.addEventListener('DOMContentLoaded', () => {
AjaxLoadingSpinner.init();
new DeleteModal(); // eslint-disable-line no-new
- initDiverganceGraph();
+ initDiverganceGraph(document.querySelector('.js-branch-list').dataset.divergingCountsEndpoint);
});
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
index ff6dadeff7d..533065b2d4d 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
@@ -1,5 +1,6 @@
<script>
-import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
+import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue';
+import { featureAccessLevelNone } from '../constants';
export default {
components: {
@@ -43,7 +44,7 @@ export default {
if (this.featureEnabled) {
return this.options;
}
- return [[0, 'Enable feature to choose access level']];
+ return [featureAccessLevelNone];
},
displaySelectInput() {
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index dea7c586868..b4d24f3aa36 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -1,16 +1,26 @@
<script>
+import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin';
+import { __ } from '~/locale';
import projectFeatureSetting from './project_feature_setting.vue';
-import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
+import projectFeatureToggle from '~/vue_shared/components/toggle_button.vue';
import projectSettingRow from './project_setting_row.vue';
-import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
+import {
+ visibilityOptions,
+ visibilityLevelDescriptions,
+ featureAccessLevelMembers,
+ featureAccessLevelEveryone,
+} from '../constants';
import { toggleHiddenClassBySelector } from '../external';
+const PAGE_FEATURE_ACCESS_LEVEL = __('Everyone');
+
export default {
components: {
projectFeatureSetting,
projectFeatureToggle,
projectSettingRow,
},
+ mixins: [settingsMixin],
props: {
currentSettings: {
@@ -37,6 +47,11 @@ export default {
required: false,
default: false,
},
+ packagesAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
visibilityHelpPath: {
type: String,
required: false,
@@ -67,8 +82,12 @@ export default {
required: false,
default: '',
},
+ packagesHelpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
-
data() {
const defaults = {
visibilityOptions,
@@ -91,9 +110,9 @@ export default {
computed: {
featureAccessLevelOptions() {
- const options = [[10, 'Only Project Members']];
+ const options = [featureAccessLevelMembers];
if (this.visibilityLevel !== visibilityOptions.PRIVATE) {
- options.push([20, 'Everyone With Access']);
+ options.push(featureAccessLevelEveryone);
}
return options;
},
@@ -106,7 +125,7 @@ export default {
pagesFeatureAccessLevelOptions() {
if (this.visibilityLevel !== visibilityOptions.PUBLIC) {
- return this.featureAccessLevelOptions.concat([[30, 'Everyone']]);
+ return this.featureAccessLevelOptions.concat([[30, PAGE_FEATURE_ACCESS_LEVEL]]);
}
return this.featureAccessLevelOptions;
},
@@ -148,24 +167,6 @@ export default {
}
},
- repositoryAccessLevel(value, oldValue) {
- if (value < oldValue) {
- // sub-features cannot have more premissive access level
- this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value);
- this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value);
-
- if (value === 0) {
- this.containerRegistryEnabled = false;
- this.lfsEnabled = false;
- }
- } else if (oldValue === 0) {
- this.mergeRequestsAccessLevel = value;
- this.buildsAccessLevel = value;
- this.containerRegistryEnabled = true;
- this.lfsEnabled = true;
- }
- },
-
issuesAccessLevel(value, oldValue) {
if (value === 0) toggleHiddenClassBySelector('.issues-feature', true);
else if (oldValue === 0) toggleHiddenClassBySelector('.issues-feature', false);
@@ -207,23 +208,20 @@ export default {
<option
:value="visibilityOptions.PRIVATE"
:disabled="!visibilityAllowed(visibilityOptions.PRIVATE)"
+ >{{ __('Private') }}</option
>
- Private
- </option>
<option
:value="visibilityOptions.INTERNAL"
:disabled="!visibilityAllowed(visibilityOptions.INTERNAL)"
+ >{{ __('Internal') }}</option
>
- Internal
- </option>
<option
:value="visibilityOptions.PUBLIC"
:disabled="!visibilityAllowed(visibilityOptions.PUBLIC)"
+ >{{ __('Public') }}</option
>
- Public
- </option>
</select>
- <i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"> </i>
+ <i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i>
</div>
</div>
<span class="form-text text-muted">{{ visibilityLevelDescription }}</span>
@@ -299,6 +297,18 @@ export default {
name="project[lfs_enabled]"
/>
</project-setting-row>
+ <project-setting-row
+ v-if="packagesAvailable"
+ :help-path="packagesHelpPath"
+ label="Packages"
+ help-text="Every project can have its own space to store its packages"
+ >
+ <project-feature-toggle
+ v-model="packagesEnabled"
+ :disabled-input="!repositoryEnabled"
+ name="project[packages_enabled]"
+ />
+ </project-setting-row>
</div>
<project-setting-row label="Wiki" help-text="Pages for project documentation">
<project-feature-setting
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
index ac0dca31c37..73269c6f3ba 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/constants.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
@@ -15,3 +15,30 @@ export const visibilityLevelDescriptions = {
'The project can be accessed by anyone, regardless of authentication.',
),
};
+
+const featureAccessLevel = {
+ NOT_ENABLED: 0,
+ PROJECT_MEMBERS: 10,
+ EVERYONE: 20,
+};
+
+const featureAccessLevelDescriptions = {
+ [featureAccessLevel.NOT_ENABLED]: __('Enable feature to choose access level'),
+ [featureAccessLevel.PROJECT_MEMBERS]: __('Only Project Members'),
+ [featureAccessLevel.EVERYONE]: __('Everyone With Access'),
+};
+
+export const featureAccessLevelNone = [
+ featureAccessLevel.NOT_ENABLED,
+ featureAccessLevelDescriptions[featureAccessLevel.NOT_ENABLED],
+];
+
+export const featureAccessLevelMembers = [
+ featureAccessLevel.PROJECT_MEMBERS,
+ featureAccessLevelDescriptions[featureAccessLevel.PROJECT_MEMBERS],
+];
+
+export const featureAccessLevelEveryone = [
+ featureAccessLevel.EVERYONE,
+ featureAccessLevelDescriptions[featureAccessLevel.EVERYONE],
+];
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/mixins/settings_pannel_mixin.js b/app/assets/javascripts/pages/projects/shared/permissions/mixins/settings_pannel_mixin.js
new file mode 100644
index 00000000000..fcbd81416f2
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/shared/permissions/mixins/settings_pannel_mixin.js
@@ -0,0 +1,26 @@
+export default {
+ data() {
+ return {
+ packagesEnabled: false,
+ };
+ },
+ watch: {
+ repositoryAccessLevel(value, oldValue) {
+ if (value < oldValue) {
+ // sub-features cannot have more premissive access level
+ this.mergeRequestsAccessLevel = Math.min(this.mergeRequestsAccessLevel, value);
+ this.buildsAccessLevel = Math.min(this.buildsAccessLevel, value);
+
+ if (value === 0) {
+ this.containerRegistryEnabled = false;
+ this.lfsEnabled = false;
+ }
+ } else if (oldValue === 0) {
+ this.mergeRequestsAccessLevel = value;
+ this.buildsAccessLevel = value;
+ this.containerRegistryEnabled = true;
+ this.lfsEnabled = true;
+ }
+ },
+ },
+};
diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
index 8f3ba9779fb..d5f1cea8356 100644
--- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue
+++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
@@ -92,7 +92,9 @@ export default {
</template>
<template v-else>
<tr>
- <td>No {{ header.toLowerCase() }} for this request.</td>
+ <td>
+ {{ sprintf(__('No %{header} for this request.'), { header: header.toLowerCase() }) }}
+ </td>
</tr>
</template>
</table>
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 48515cf785c..185003c306e 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -5,6 +5,7 @@ import { glEmojiTag } from '~/emoji';
import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue';
import simpleMetric from './simple_metric.vue';
+import { s__ } from '~/locale';
export default {
components: {
@@ -35,10 +36,10 @@ export default {
},
},
detailedMetrics: [
- { metric: 'pg', header: 'SQL queries', details: 'queries', keys: ['sql'] },
+ { metric: 'pg', header: s__('PerformanceBar|SQL queries'), details: 'queries', keys: ['sql'] },
{
metric: 'gitaly',
- header: 'Gitaly calls',
+ header: s__('PerformanceBar|Gitaly calls'),
details: 'details',
keys: ['feature', 'request'],
},
@@ -99,7 +100,8 @@ export default {
class="current-host"
:class="{ canary: currentRequest.details.host.canary }"
>
- <span v-html="birdEmoji"></span> {{ currentRequest.details.host.hostname }}
+ <span v-html="birdEmoji"></span>
+ {{ currentRequest.details.host.hostname }}
</span>
</div>
<detailed-metric
@@ -118,9 +120,9 @@ export default {
data-toggle="modal"
data-target="#modal-peek-line-profile"
>
- profile
+ {{ s__('PerformanceBar|profile') }}
</button>
- <a v-else :href="profileUrl"> profile </a>
+ <a v-else :href="profileUrl">{{ s__('PerformanceBar|profile') }}</a>
</div>
<simple-metric
v-for="metric in $options.simpleMetrics"
@@ -139,7 +141,7 @@ export default {
id="peek-view-trace"
class="view"
>
- <a :href="currentRequest.details.tracing.tracing_url"> trace </a>
+ <a :href="currentRequest.details.tracing.tracing_url">{{ s__('PerformanceBar|trace') }}</a>
</div>
<request-selector
v-if="currentRequest"
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index b2e365e5cde..39afa87afc3 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -2,6 +2,7 @@
import { GlLoadingIcon } from '@gitlab/ui';
import ciHeader from '../../vue_shared/components/header_ci_component.vue';
import eventHub from '../event_hub';
+import { __ } from '~/locale';
export default {
name: 'PipelineHeaderSection',
@@ -54,7 +55,7 @@ export default {
if (this.pipeline.retry_path) {
actions.push({
- label: 'Retry',
+ label: __('Retry'),
path: this.pipeline.retry_path,
cssClass: 'js-retry-button btn btn-inverted-secondary',
type: 'button',
@@ -64,7 +65,7 @@ export default {
if (this.pipeline.cancel_path) {
actions.push({
- label: 'Cancel running',
+ label: __('Cancel running'),
path: this.pipeline.cancel_path,
cssClass: 'js-btn-cancel-pipeline btn btn-danger',
type: 'button',
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 65a2b61396c..3f021a26ec5 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -94,9 +94,8 @@ export default {
tabindex="0"
class="js-pipeline-url-autodevops badge badge-info autodevops-badge"
role="button"
+ >{{ __('Auto DevOps') }}</gl-link
>
- Auto DevOps
- </gl-link>
<span v-if="pipeline.flags.stuck" class="js-pipeline-url-stuck badge badge-warning">
{{ __('stuck') }}
</span>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue
index 03d332cd430..d3ba0c97f6b 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue
@@ -44,6 +44,11 @@ export default {
cancelingPipeline: null,
};
},
+ watch: {
+ pipelines() {
+ this.cancelingPipeline = null;
+ },
+ },
created() {
eventHub.$on('openConfirmationModal', this.setModalData);
},
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index e32e2f785bd..5275de3bc8b 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -241,7 +241,11 @@ export default {
return this.cancelingPipeline === this.pipeline.id;
},
},
-
+ watch: {
+ pipeline() {
+ this.isRetrying = false;
+ },
+ },
methods: {
handleCancelClick() {
eventHub.$emit('openConfirmationModal', {
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index 3cc9d0a3a4e..a6243366375 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -107,8 +107,8 @@ export default {
}
// Stop polling
this.poll.stop();
- // Update the table
- return this.getPipelines().then(() => this.poll.restart());
+ // Restarting the poll also makes an initial request
+ this.poll.restart();
},
fetchPipelines() {
if (!this.isMakingRequest) {
@@ -153,7 +153,7 @@ export default {
postAction(endpoint) {
this.service
.postAction(endpoint)
- .then(() => this.fetchPipelines())
+ .then(() => this.updateTable())
.catch(() => Flash(__('An error occurred while making the request.')));
},
},
diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
index bfc55013a71..03281aa1317 100644
--- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
+++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
@@ -3,7 +3,7 @@ import Visibility from 'visibilityjs';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
import Poll from '~/lib/utils/poll';
import Flash from '~/flash';
-import { s__, sprintf } from '~/locale';
+import { __, s__, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import { GlLoadingIcon } from '@gitlab/ui';
import CommitPipelineService from '../services/commit_pipeline_service';
@@ -56,7 +56,7 @@ export default {
},
errorCallback() {
this.ciStatus = {
- text: 'not found',
+ text: __('not found'),
icon: 'status_notfound',
group: 'notfound',
};
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index f510b905a2e..c82b65cd97b 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -4,7 +4,7 @@ import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
-import { sprintf } from '../../locale';
+import { __, sprintf } from '../../locale';
export default {
name: 'ReleaseBlock',
@@ -27,13 +27,13 @@ export default {
},
computed: {
releasedTimeAgo() {
- return sprintf('released %{time}', {
+ return sprintf(__('released %{time}'), {
time: this.timeFormated(this.release.created_at),
});
},
userImageAltDescription() {
return this.author && this.author.username
- ? sprintf("%{username}'s avatar", { username: this.author.username })
+ ? sprintf(__("%{username}'s avatar"), { username: this.author.username })
: null;
},
commit() {
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index f25cee9bb57..26493556063 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -1,10 +1,9 @@
<script>
-import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
+import { GlTooltipDirective, GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import Icon from '../../vue_shared/components/icon.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
-import CommitPipelineStatus from '../../projects/tree/components/commit_pipeline_status_component.vue';
import CiIcon from '../../vue_shared/components/ci_icon.vue';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
import getRefMixin from '../mixins/get_ref';
@@ -16,11 +15,11 @@ export default {
Icon,
UserAvatarLink,
TimeagoTooltip,
- CommitPipelineStatus,
ClipboardButton,
CiIcon,
GlLink,
GlButton,
+ GlLoadingIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -39,7 +38,10 @@ export default {
path: this.currentPath.replace(/^\//, ''),
};
},
- update: data => data.project.repository.tree.commit,
+ update: data => data.project.repository.tree.lastCommit,
+ context: {
+ isSingleRequest: true,
+ },
},
},
props: {
@@ -59,14 +61,14 @@ export default {
computed: {
statusTitle() {
return sprintf(s__('Commits|Commit: %{commitText}'), {
- commitText: this.commit.pipeline.detailedStatus.text,
+ commitText: this.commit.latestPipeline.detailedStatus.text,
});
},
isLoading() {
return this.$apollo.queries.commit.loading;
},
showCommitId() {
- return this.commit.id.substr(0, 8);
+ return this.commit.sha.substr(0, 8);
},
},
methods: {
@@ -78,68 +80,75 @@ export default {
</script>
<template>
- <div v-if="!isLoading" class="info-well d-none d-sm-flex project-last-commit commit p-3">
- <user-avatar-link
- v-if="commit.author"
- :link-href="commit.author.webUrl"
- :img-src="commit.author.avatarUrl"
- :img-size="40"
- class="avatar-cell"
- />
- <div class="commit-detail flex-list">
- <div class="commit-content qa-commit-content">
- <gl-link :href="commit.webUrl" class="commit-row-message item-title">
- {{ commit.title }}
- </gl-link>
- <gl-button
- v-if="commit.description"
- :class="{ open: showDescription }"
- :aria-label="__('Show commit description')"
- class="text-expander"
- @click="toggleShowDescription"
- >
- <icon name="ellipsis_h" />
- </gl-button>
- <div class="committer">
+ <div class="info-well d-none d-sm-flex project-last-commit commit p-3">
+ <gl-loading-icon v-if="isLoading" size="md" class="mx-auto" />
+ <template v-else>
+ <user-avatar-link
+ v-if="commit.author"
+ :link-href="commit.author.webUrl"
+ :img-src="commit.author.avatarUrl"
+ :img-size="40"
+ class="avatar-cell"
+ />
+ <div class="commit-detail flex-list">
+ <div class="commit-content qa-commit-content">
+ <gl-link :href="commit.webUrl" class="commit-row-message item-title">
+ {{ commit.title }}
+ </gl-link>
+ <gl-button
+ v-if="commit.description"
+ :class="{ open: showDescription }"
+ :aria-label="__('Show commit description')"
+ class="text-expander"
+ @click="toggleShowDescription"
+ >
+ <icon name="ellipsis_h" />
+ </gl-button>
+ <div class="committer">
+ <gl-link
+ v-if="commit.author"
+ :href="commit.author.webUrl"
+ class="commit-author-link js-user-link"
+ >
+ {{ commit.author.name }}
+ </gl-link>
+ authored
+ <timeago-tooltip :time="commit.authoredDate" tooltip-placement="bottom" />
+ </div>
+ <pre
+ v-if="commit.description"
+ v-show="showDescription"
+ class="commit-row-description append-bottom-8"
+ >
+ {{ commit.description }}
+ </pre>
+ </div>
+ <div class="commit-actions flex-row">
<gl-link
- v-if="commit.author"
- :href="commit.author.webUrl"
- class="commit-author-link js-user-link"
+ v-if="commit.latestPipeline"
+ v-gl-tooltip
+ :href="commit.latestPipeline.detailedStatus.detailsPath"
+ :title="statusTitle"
+ class="js-commit-pipeline"
>
- {{ commit.author.name }}
+ <ci-icon
+ :status="commit.latestPipeline.detailedStatus"
+ :size="24"
+ :aria-label="statusTitle"
+ />
</gl-link>
- authored
- <timeago-tooltip :time="commit.authoredDate" tooltip-placement="bottom" />
- </div>
- <pre
- v-if="commit.description"
- v-show="showDescription"
- class="commit-row-description append-bottom-8"
- >
- {{ commit.description }}
- </pre>
- </div>
- <div class="commit-actions flex-row">
- <gl-link
- v-if="commit.pipeline"
- v-gl-tooltip
- :href="commit.pipeline.detailedStatus.detailsPath"
- :title="statusTitle"
- class="js-commit-pipeline"
- >
- <ci-icon :status="commit.pipeline.detailedStatus" :size="24" :aria-label="statusTitle" />
- </gl-link>
- <div class="commit-sha-group d-flex">
- <div class="label label-monospace monospace">
- {{ showCommitId }}
+ <div class="commit-sha-group d-flex">
+ <div class="label label-monospace monospace">
+ {{ showCommitId }}
+ </div>
+ <clipboard-button
+ :text="commit.sha"
+ :title="__('Copy commit SHA to clipboard')"
+ tooltip-placement="bottom"
+ />
</div>
- <clipboard-button
- :text="commit.id"
- :title="__('Copy commit SHA to clipboard')"
- tooltip-placement="bottom"
- />
</div>
</div>
- </div>
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index c31e7fa71a2..3e060e9ecb6 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -110,9 +110,7 @@ export default {
<component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated">
{{ fullPath }}
</component>
- <gl-badge v-if="lfsOid" variant="default" class="label-lfs ml-1">
- LFS
- </gl-badge>
+ <gl-badge v-if="lfsOid" variant="default" class="label-lfs ml-1">LFS</gl-badge>
<template v-if="isSubmodule">
@ <gl-link href="#" class="commit-sha">{{ shortSha }}</gl-link>
</template>
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 6280977b05b..ea051eaa414 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -50,23 +50,19 @@ export default function setupVueRepositoryList() {
},
});
- const commitEl = document.getElementById('js-last-commit');
-
- if (commitEl) {
- // eslint-disable-next-line no-new
- new Vue({
- el: commitEl,
- router,
- apolloProvider,
- render(h) {
- return h(LastCommit, {
- props: {
- currentPath: this.$route.params.pathMatch,
- },
- });
- },
- });
- }
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: document.getElementById('js-last-commit'),
+ router,
+ apolloProvider,
+ render(h) {
+ return h(LastCommit, {
+ props: {
+ currentPath: this.$route.params.pathMatch,
+ },
+ });
+ },
+ });
return new Vue({
el,
diff --git a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
index 90901f54d54..3bdfd979fa4 100644
--- a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
+++ b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
@@ -2,8 +2,8 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
project(fullPath: $projectPath) {
repository {
tree(path: $path, ref: $ref) {
- commit {
- id
+ lastCommit {
+ sha
title
message
webUrl
@@ -13,7 +13,7 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
avatarUrl
webUrl
}
- pipeline {
+ latestPipeline {
detailedStatus {
detailsPath
icon
diff --git a/app/assets/javascripts/serverless/components/area.vue b/app/assets/javascripts/serverless/components/area.vue
index 32c9d6eccb8..a1a8cd3acbd 100644
--- a/app/assets/javascripts/serverless/components/area.vue
+++ b/app/assets/javascripts/serverless/components/area.vue
@@ -4,6 +4,7 @@ import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import dateFormat from 'dateformat';
import { X_INTERVAL } from '../constants';
import { validateGraphData } from '../utils';
+import { __ } from '~/locale';
let debouncedResize;
@@ -42,7 +43,7 @@ export default {
},
generateSeries() {
return {
- name: 'Invocations',
+ name: __('Invocations'),
type: 'line',
data: this.chartData.requests.map(data => [data.time, data.value]),
symbolSize: 0,
@@ -124,7 +125,9 @@ export default {
<div class="prometheus-graph">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
- <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
+ <div ref="graphWidgets" class="prometheus-graph-widgets">
+ <slot></slot>
+ </div>
</div>
<gl-area-chart
ref="areaChart"
@@ -135,12 +138,8 @@ export default {
:width="width"
:include-legend-avg-max="false"
>
- <template slot="tooltipTitle">
- {{ tooltipPopoverTitle }}
- </template>
- <template slot="tooltipContent">
- {{ tooltipPopoverContent }}
- </template>
+ <template slot="tooltipTitle">{{ tooltipPopoverTitle }}</template>
+ <template slot="tooltipContent">{{ tooltipPopoverContent }}</template>
</gl-area-chart>
</div>
</template>
diff --git a/app/assets/javascripts/serverless/components/function_details.vue b/app/assets/javascripts/serverless/components/function_details.vue
index b8906cfca4e..d542dad8119 100644
--- a/app/assets/javascripts/serverless/components/function_details.vue
+++ b/app/assets/javascripts/serverless/components/function_details.vue
@@ -89,7 +89,9 @@ export default {
}}
</p>
</div>
- <div v-else><p>No pods loaded at this time.</p></div>
+ <div v-else>
+ <p>{{ s__('ServerlessDetails|No pods loaded at this time.') }}</p>
+ </div>
<area-chart v-if="hasPrometheusData" :graph-data="graphData" :container-width="elWidth" />
<missing-prometheus
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
index 94341050b86..9e66869515c 100644
--- a/app/assets/javascripts/serverless/components/functions.vue
+++ b/app/assets/javascripts/serverless/components/functions.vue
@@ -1,4 +1,5 @@
<script>
+import { sprintf, s__ } from '~/locale';
import { mapState, mapActions, mapGetters } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import FunctionRow from './function_row.vue';
@@ -37,6 +38,28 @@ export default {
isInstalled() {
return this.installed === true;
},
+ noServerlessConfigFile() {
+ return sprintf(
+ s__(
+ 'Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file.',
+ ),
+ { startTag: '<code>', endTag: '</code>' },
+ );
+ },
+ noGitlabYamlConfigured() {
+ return sprintf(
+ s__('Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured.'),
+ { startTag: '<code>', endTag: '</code>' },
+ );
+ },
+ mismatchedServerlessFunctions() {
+ return sprintf(
+ s__(
+ "Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster.",
+ ),
+ { startTag: '<code>', endTag: '</code>' },
+ );
+ },
},
created() {
this.fetchFunctions({
@@ -82,25 +105,29 @@ export default {
<h4 class="state-title text-center">{{ s__('Serverless|No functions available') }}</h4>
<p class="state-description">
{{
- s__(`Serverless|There is currently no function data available from Knative.
- This could be for a variety of reasons including:`)
+ s__(
+ 'Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:',
+ )
}}
</p>
<ul>
- <li>Your repository does not have a corresponding <code>serverless.yml</code> file.</li>
- <li>Your <code>.gitlab-ci.yml</code> file is not properly configured.</li>
<li>
- The functions listed in the <code>serverless.yml</code> file don't match the namespace
- of your cluster.
+ {{ noServerlessConfigFile }}
+ </li>
+ <li>
+ {{ noGitlabYamlConfigured }}
+ </li>
+ <li>
+ {{ mismatchedServerlessFunctions }}
</li>
- <li>The deploy job has not finished.</li>
+ <li>{{ s__('Serverless|The deploy job has not finished.') }}</li>
</ul>
<p>
{{
- s__(`Serverless|If you believe none of these apply, please check
- back later as the function data may be in the process of becoming
- available.`)
+ s__(
+ 'Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available.',
+ )
}}
</p>
<div class="text-center">
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 41386178a1e..a79da476890 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -162,7 +162,8 @@ export default {
removeWIPPath: store.removeWIPPath,
sourceBranchPath: store.sourceBranchPath,
ciEnvironmentsStatusPath: store.ciEnvironmentsStatusPath,
- statusPath: store.statusPath,
+ mergeRequestBasicPath: store.mergeRequestBasicPath,
+ mergeRequestWidgetPath: store.mergeRequestWidgetPath,
mergeActionsContentPath: store.mergeActionsContentPath,
rebasePath: store.rebasePath,
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
index 0bb70bfd658..1dae53039d5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
+++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
@@ -30,11 +30,11 @@ export default class MRWidgetService {
}
poll() {
- return axios.get(`${this.endpoints.statusPath}?serializer=basic`);
+ return axios.get(this.endpoints.mergeRequestBasicPath);
}
checkStatus() {
- return axios.get(`${this.endpoints.statusPath}?serializer=widget`);
+ return axios.get(this.endpoints.mergeRequestWidgetPath);
}
fetchMergeActionsContent() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index bfa3e7f4a59..581fee7477f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -86,7 +86,8 @@ export default class MergeRequestStore {
this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased);
- this.statusPath = data.status_path;
+ this.mergeRequestBasicPath = data.merge_request_basic_path;
+ this.mergeRequestWidgetPath = data.merge_request_widget_path;
this.emailPatchesPath = data.email_patches_path;
this.plainDiffPath = data.plain_diff_path;
this.newBlobPath = data.new_blob_path;
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 1bfa91500cb..fe5289ff371 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -131,7 +131,7 @@ export default {
</script>
<template>
- <div>
+ <div v-if="!file.moved">
<file-header v-if="file.isHeader" :path="file.path" />
<div
v-else
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index a2f518cd24e..d5ef66af31a 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -11,10 +11,10 @@
// like a table or typography then make changes in the framework/ directory.
// If you need to add unique style that should affect only one page - use pages/
// directory.
-@import "../../../node_modules/at.js/dist/css/jquery.atwho";
-@import "../../../node_modules/pikaday/scss/pikaday";
-@import "../../../node_modules/dropzone/dist/basic";
-@import "../../../node_modules/select2/select2";
+@import "at.js/dist/css/jquery.atwho";
+@import "pikaday/scss/pikaday";
+@import "dropzone/dist/basic";
+@import "select2/select2";
// GitLab UI framework
@import "framework";
diff --git a/app/assets/stylesheets/csslab.scss b/app/assets/stylesheets/csslab.scss
index acaa41e2677..87c59cd42c0 100644
--- a/app/assets/stylesheets/csslab.scss
+++ b/app/assets/stylesheets/csslab.scss
@@ -1 +1 @@
-@import "../../../node_modules/@gitlab/csslab/dist/css/csslab-slim";
+@import "@gitlab/csslab/dist/css/csslab-slim";
diff --git a/app/assets/stylesheets/errors.scss b/app/assets/stylesheets/errors.scss
index 8c32b6c8985..d287215096e 100644
--- a/app/assets/stylesheets/errors.scss
+++ b/app/assets/stylesheets/errors.scss
@@ -2,12 +2,12 @@
* This is a minimal stylesheet, meant to be used for error pages.
*/
@import 'framework/variables';
-@import '../../../node_modules/bootstrap/scss/functions';
-@import '../../../node_modules/bootstrap/scss/variables';
-@import '../../../node_modules/bootstrap/scss/mixins';
-@import '../../../node_modules/bootstrap/scss/reboot';
-@import '../../../node_modules/bootstrap/scss/buttons';
-@import '../../../node_modules/bootstrap/scss/forms';
+@import 'bootstrap/scss/functions';
+@import 'bootstrap/scss/variables';
+@import 'bootstrap/scss/mixins';
+@import 'bootstrap/scss/reboot';
+@import 'bootstrap/scss/buttons';
+@import 'bootstrap/scss/forms';
$body-color: #666;
$header-color: #456;
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 14f4652e847..9b1d9d51f9c 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -2,7 +2,7 @@
@import 'framework/variables_overrides';
@import 'framework/mixins';
-@import '../../../node_modules/@gitlab/ui/scss/gitlab_ui';
+@import '@gitlab/ui/scss/gitlab_ui';
@import 'bootstrap_migration';
@import 'framework/layout';
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 5e3652db48f..343cca96851 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -92,9 +92,20 @@
width: 400px;
}
- &.is-expandable {
- .board-header {
- cursor: pointer;
+ .board-title-caret {
+ cursor: pointer;
+ border-radius: $border-radius-default;
+ padding: 4px;
+
+ &:hover {
+ background-color: $gray-dark;
+ transition: background-color 0.1s linear;
+ }
+ }
+
+ &:not(.is-collapsed) {
+ .board-title-caret {
+ margin: 0 $gl-padding-4 0 -10px;
}
}
@@ -102,20 +113,51 @@
width: 50px;
.board-title {
- > span {
- width: 100%;
- margin-top: -12px;
+ flex-direction: column;
+ height: 100%;
+ padding: $gl-padding-8 0;
+ }
+
+ .board-title-caret {
+ margin-top: 1px;
+ }
+
+ .user-avatar-link,
+ .milestone-icon {
+ margin-top: $gl-padding-8;
+ transform: rotate(90deg);
+ }
+
+ .board-title-text {
+ flex-grow: 0;
+ margin: $gl-padding-8 0;
+
+ .board-title-main-text {
display: block;
- transform: rotate(90deg) translate(35px, 0);
- overflow: initial;
+ }
+
+ .board-title-sub-text {
+ display: none;
}
}
- .board-title-expandable-toggle {
- position: absolute;
- top: 50%;
- left: 50%;
- margin-left: -10px;
+ .issue-count-badge {
+ border: 0;
+ white-space: nowrap;
+ }
+
+ .board-title-text > span,
+ .issue-count-badge > span {
+ height: 16px;
+
+ // Force the height to be equal to the parent's width while centering the contents.
+ // The contents *should* be about 16 px.
+ // We do this because the flow of elements isn't affected by the rotate transform, so we must ensure that a
+ // rotated element has square dimensions so it won't overlap with its siblings.
+ margin: calc(50% - 8px) 0;
+
+ transform: rotate(90deg);
+ transform-origin: center;
}
}
}
@@ -152,12 +194,14 @@
}
.board-title {
+ align-items: center;
font-size: 1em;
border-bottom: 1px solid $border-color;
+ padding: $gl-padding-8 $gl-padding;
}
.board-title-text {
- margin: $gl-vert-padding auto $gl-vert-padding 0;
+ flex-grow: 1;
}
.board-delete {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 824edb2869f..e880b941d67 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -657,6 +657,10 @@ $note-form-margin-left: 72px;
margin-left: -1px;
}
+ .btn-group > .discussion-create-issue-btn {
+ margin-left: -2px;
+ }
+
svg {
height: 15px;
}
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index 65d14781d92..d43f5393ecc 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -3,6 +3,7 @@
class Dashboard::ProjectsController < Dashboard::ApplicationController
include ParamsBackwardCompatibility
include RendersMemberAccess
+ include OnboardingExperimentHelper
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 316da8f129d..797833e3f91 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -201,8 +201,7 @@ class GroupsController < Groups::ApplicationController
params[:sort] ||= 'latest_activity_desc'
options = {}
- options[:only_owned] = true if params[:shared] == '0'
- options[:only_shared] = true if params[:shared] == '1'
+ options[:include_subgroups] = true
@projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user)
.execute
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index fc708400657..d77f64a84f5 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -25,15 +25,6 @@ class Projects::BranchesController < Projects::ApplicationController
@refs_pipelines = @project.ci_pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names = repository.merged_branch_names(@branches.map(&:name))
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/48097
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- @max_commits = @branches.reduce(0) do |memo, branch|
- diverging_commit_counts = repository.diverging_commit_counts(branch)
- [memo, diverging_commit_counts.values_at(:behind, :ahead, :distance)]
- .flatten.compact.max
- end
- end
-
# https://gitlab.com/gitlab-org/gitlab-ce/issues/48097
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
@@ -51,6 +42,19 @@ class Projects::BranchesController < Projects::ApplicationController
@branches = @repository.recent_branches
end
+ def diverging_commit_counts
+ respond_to do |format|
+ format.json do
+ service = Branches::DivergingCommitCountsService.new(repository)
+ branches = BranchesFinder.new(repository, params.permit(names: [])).execute
+
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ render json: branches.to_h { |branch| [branch.name, service.call(branch)] }
+ end
+ end
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def create
branch_name = strip_tags(sanitize(params[:branch_name]))
@@ -64,8 +68,9 @@ class Projects::BranchesController < Projects::ApplicationController
success = (result[:status] == :success)
if params[:issue_iid] && success
- issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid])
- SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue
+ target_project = confidential_issue_project || @project
+ issue = IssuesFinder.new(current_user, project_id: target_project.id).find_by(iid: params[:issue_iid])
+ SystemNoteService.new_issue_branch(issue, target_project, current_user, branch_name, branch_project: @project) if issue
end
respond_to do |format|
@@ -162,4 +167,15 @@ class Projects::BranchesController < Projects::ApplicationController
@branches = Kaminari.paginate_array(@branches).page(params[:page])
end
end
+
+ def confidential_issue_project
+ return unless Feature.enabled?(:create_confidential_merge_request, @project)
+ return if params[:confidential_issue_project_id].blank?
+
+ confidential_issue_project = Project.find(params[:confidential_issue_project_id])
+
+ return unless can?(current_user, :update_issue, confidential_issue_project)
+
+ confidential_issue_project
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index f221f0363d3..e275b417784 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -172,6 +172,7 @@ class Projects::IssuesController < Projects::ApplicationController
def create_merge_request
create_params = params.slice(:branch_name, :ref).merge(issue_iid: issue.iid)
+ create_params[:target_project_id] = params[:target_project_id] if Feature.enabled?(:create_confidential_merge_request, @project)
result = ::MergeRequests::CreateFromIssueService.new(project, current_user, create_params).execute
if result[:status] == :success
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index f2a6268b3e9..dcc272aecff 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -51,4 +51,11 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
Ci::Pipeline.none
end
end
+
+ def close_merge_request_if_no_source_project
+ return if @merge_request.source_project
+ return unless @merge_request.open?
+
+ @merge_request.close
+ end
end
diff --git a/app/controllers/projects/merge_requests/content_controller.rb b/app/controllers/projects/merge_requests/content_controller.rb
new file mode 100644
index 00000000000..6e026b83ee3
--- /dev/null
+++ b/app/controllers/projects/merge_requests/content_controller.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class Projects::MergeRequests::ContentController < Projects::MergeRequests::ApplicationController
+ # @merge_request.check_mergeability is not executed here since
+ # widget serializer calls it via mergeable? method
+ # but we might want to call @merge_request.check_mergeability
+ # for other types of serialization
+
+ before_action :close_merge_request_if_no_source_project
+ around_action :allow_gitaly_ref_name_caching
+
+ def widget
+ respond_to do |format|
+ format.json do
+ Gitlab::PollingInterval.set_header(response, interval: 10_000)
+
+ serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
+ render json: serializer.represent(merge_request, serializer: 'widget')
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index fc37ce1dbc4..7ee8e0ea8f8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -235,12 +235,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
params[:auto_merge_strategy].present? || params[:merge_when_pipeline_succeeds].present?
end
- def close_merge_request_if_no_source_project
- if !@merge_request.source_project && @merge_request.open?
- @merge_request.close
- end
- end
-
private
def ci_environments_status_on_merge_result?
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 45d5591e81b..b462c8053fa 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -9,6 +9,7 @@ class BranchesFinder
def execute
branches = repository.branches_sorted_by(sort)
branches = by_search(branches)
+ branches = by_names(branches)
branches
end
@@ -16,6 +17,10 @@ class BranchesFinder
attr_reader :repository, :params
+ def names
+ @params[:names].presence
+ end
+
def search
@params[:search].presence
end
@@ -59,4 +64,13 @@ class BranchesFinder
def find_exact_match_index(matches, term)
matches.index { |branch| branch.name.casecmp(term) == 0 }
end
+
+ def by_names(branches)
+ return branches unless names
+
+ branch_names = names.to_set
+ branches.filter do |branch|
+ branch_names.include?(branch.name)
+ end
+ end
end
diff --git a/app/graphql/mutations/.keep b/app/graphql/mutations/.keep
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/graphql/mutations/.keep
+++ /dev/null
diff --git a/app/graphql/mutations/award_emojis/add.rb b/app/graphql/mutations/award_emojis/add.rb
new file mode 100644
index 00000000000..8e050dd6d29
--- /dev/null
+++ b/app/graphql/mutations/award_emojis/add.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AwardEmojis
+ class Add < Base
+ graphql_name 'AddAwardEmoji'
+
+ def resolve(args)
+ awardable = authorized_find!(id: args[:awardable_id])
+
+ check_object_is_awardable!(awardable)
+
+ # TODO this will be handled by AwardEmoji::AddService
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
+ award = awardable.create_award_emoji(args[:name], current_user)
+
+ {
+ award_emoji: (award if award.persisted?),
+ errors: errors_on_object(award)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/award_emojis/base.rb b/app/graphql/mutations/award_emojis/base.rb
new file mode 100644
index 00000000000..d868db84f9d
--- /dev/null
+++ b/app/graphql/mutations/award_emojis/base.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AwardEmojis
+ class Base < BaseMutation
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ authorize :award_emoji
+
+ argument :awardable_id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the awardable resource'
+
+ argument :name,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::AwardEmojis::AwardEmojiType, :name)
+
+ field :award_emoji,
+ Types::AwardEmojis::AwardEmojiType,
+ null: true,
+ description: 'The award emoji after mutation'
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id)
+ end
+
+ # Called by mutations methods after performing an authorization check
+ # of an awardable object.
+ def check_object_is_awardable!(object)
+ unless object.is_a?(Awardable) && object.emoji_awardable?
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Cannot award emoji to this resource'
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/award_emojis/remove.rb b/app/graphql/mutations/award_emojis/remove.rb
new file mode 100644
index 00000000000..3ba85e445b8
--- /dev/null
+++ b/app/graphql/mutations/award_emojis/remove.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AwardEmojis
+ class Remove < Base
+ graphql_name 'RemoveAwardEmoji'
+
+ def resolve(args)
+ awardable = authorized_find!(id: args[:awardable_id])
+
+ check_object_is_awardable!(awardable)
+
+ # TODO this check can be removed once AwardEmoji services are available.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
+ unless awardable.awarded_emoji?(args[:name], current_user)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'You have not awarded emoji of type name to the awardable'
+ end
+
+ # TODO this will be handled by AwardEmoji::DestroyService
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
+ awardable.remove_award_emoji(args[:name], current_user)
+
+ {
+ # Mutation response is always a `nil` award_emoji
+ errors: []
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/award_emojis/toggle.rb b/app/graphql/mutations/award_emojis/toggle.rb
new file mode 100644
index 00000000000..c03902e8035
--- /dev/null
+++ b/app/graphql/mutations/award_emojis/toggle.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Mutations
+ module AwardEmojis
+ class Toggle < Base
+ graphql_name 'ToggleAwardEmoji'
+
+ field :toggledOn,
+ GraphQL::BOOLEAN_TYPE,
+ null: false,
+ description: 'True when the emoji was awarded, false when it was removed'
+
+ def resolve(args)
+ awardable = authorized_find!(id: args[:awardable_id])
+
+ check_object_is_awardable!(awardable)
+
+ # TODO this will be handled by AwardEmoji::ToggleService
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
+ award = awardable.toggle_award_emoji(args[:name], current_user)
+
+ # Destroy returns a collection :(
+ award = award.first if award.is_a?(Array)
+
+ errors = errors_on_object(award)
+
+ toggled_on = awardable.awarded_emoji?(args[:name], current_user)
+
+ {
+ # For consistency with the AwardEmojis::Remove mutation, only return
+ # the AwardEmoji if it was created and not destroyed
+ award_emoji: (award if toggled_on),
+ errors: errors,
+ toggled_on: toggled_on
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index eb03dfe1624..08d2a1f18a3 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -2,6 +2,8 @@
module Mutations
class BaseMutation < GraphQL::Schema::RelayClassicMutation
+ prepend Gitlab::Graphql::CopyFieldDescription
+
field :errors, [GraphQL::STRING_TYPE],
null: false,
description: "Reasons why the mutation failed."
@@ -9,5 +11,10 @@ module Mutations
def current_user
context[:current_user]
end
+
+ # Returns Array of errors on an ActiveRecord object
+ def errors_on_object(record)
+ record.errors.full_messages
+ end
end
end
diff --git a/app/graphql/types/award_emojis/award_emoji_type.rb b/app/graphql/types/award_emojis/award_emoji_type.rb
new file mode 100644
index 00000000000..8daf699a112
--- /dev/null
+++ b/app/graphql/types/award_emojis/award_emoji_type.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Types
+ module AwardEmojis
+ class AwardEmojiType < BaseObject
+ graphql_name 'AwardEmoji'
+
+ authorize :read_emoji
+
+ present_using AwardEmojiPresenter
+
+ field :name,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The emoji name'
+
+ field :description,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The emoji description'
+
+ field :unicode,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The emoji in unicode'
+
+ field :emoji,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The emoji as an icon'
+
+ field :unicode_version,
+ GraphQL::STRING_TYPE,
+ null: false,
+ description: 'The unicode version for this emoji'
+
+ field :user,
+ Types::UserType,
+ null: false,
+ description: 'The user who awarded the emoji',
+ resolve: -> (award_emoji, _args, _context) {
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(User, award_emoji.user_id).find
+ }
+ end
+ end
+end
diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb
new file mode 100644
index 00000000000..d73dd73affd
--- /dev/null
+++ b/app/graphql/types/commit_type.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Types
+ class CommitType < BaseObject
+ graphql_name 'Commit'
+
+ authorize :download_code
+
+ present_using CommitPresenter
+
+ field :id, type: GraphQL::ID_TYPE, null: false
+ field :sha, type: GraphQL::STRING_TYPE, null: false
+ field :title, type: GraphQL::STRING_TYPE, null: true
+ field :description, type: GraphQL::STRING_TYPE, null: true
+ field :message, type: GraphQL::STRING_TYPE, null: true
+ field :authored_date, type: Types::TimeType, null: true
+ field :web_url, type: GraphQL::STRING_TYPE, null: false
+
+ # models/commit lazy loads the author by email
+ field :author, type: Types::UserType, null: true
+
+ field :latest_pipeline,
+ type: Types::Ci::PipelineType,
+ null: true,
+ description: "Latest pipeline for this commit",
+ resolve: -> (obj, ctx, args) do
+ Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index 2b4ef299296..6ef1d816b7c 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -6,6 +6,9 @@ module Types
graphql_name "Mutation"
+ mount_mutation Mutations::AwardEmojis::Add
+ mount_mutation Mutations::AwardEmojis::Remove
+ mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::MergeRequests::SetWip
end
end
diff --git a/app/graphql/types/tree/tree_type.rb b/app/graphql/types/tree/tree_type.rb
index 1ee93ed9542..cbc448a0695 100644
--- a/app/graphql/types/tree/tree_type.rb
+++ b/app/graphql/types/tree/tree_type.rb
@@ -4,6 +4,11 @@ module Types
class TreeType < BaseObject
graphql_name 'Tree'
+ # Complexity 10 as it triggers a Gitaly call on each render
+ field :last_commit, Types::CommitType, null: true, complexity: 10, resolve: -> (tree, args, ctx) do
+ tree.repository.last_commit_for_path(tree.sha, tree.path)
+ end
+
field :trees, Types::Tree::TreeEntryType.connection_type, null: false, resolve: -> (obj, args, ctx) do
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository)
end
diff --git a/app/helpers/onboarding_experiment_helper.rb b/app/helpers/onboarding_experiment_helper.rb
new file mode 100644
index 00000000000..ad49d333d7a
--- /dev/null
+++ b/app/helpers/onboarding_experiment_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module OnboardingExperimentHelper
+ def allow_access_to_onboarding?
+ ::Gitlab.com? && Feature.enabled?(:user_onboarding)
+ end
+end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 506c8d251b7..04db1980b99 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -51,7 +51,7 @@ module Emails
def note_thread_options(recipient_id)
{
from: sender(@note.author_id),
- to: recipient(recipient_id, @group),
+ to: recipient(recipient_id, @project&.group || @group),
subject: subject("#{@note.noteable.title} (#{@note.noteable.reference_link_text})")
}
end
@@ -60,7 +60,7 @@ module Emails
# `note_id` is a `Note` when originating in `NotifyPreview`
@note = note_id.is_a?(Note) ? note_id : Note.find(note_id)
@project = @note.project
- @group = @project.try(:group) || @note.noteable.try(:group)
+ @group = @note.noteable.try(:group)
if (@project || @group) && @note.persisted?
@sent_notification = SentNotification.record_note(@note, recipient_id, reply_key)
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index cd645850af3..fbd8036653a 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -23,11 +23,6 @@ class ApplicationSetting < ApplicationRecord
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
- ignore_column :circuitbreaker_failure_count_threshold
- ignore_column :circuitbreaker_failure_reset_time
- ignore_column :circuitbreaker_storage_timeout
- ignore_column :circuitbreaker_access_retries
- ignore_column :circuitbreaker_check_interval
ignore_column :koding_url
ignore_column :koding_enabled
ignore_column :sentry_enabled
diff --git a/app/models/board.rb b/app/models/board.rb
index e08db764f65..50b6ca9b70f 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -4,11 +4,14 @@ class Board < ApplicationRecord
belongs_to :group
belongs_to :project
- has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+ has_many :lists, -> { ordered }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
+ has_many :destroyable_lists, -> { destroyable.ordered }, class_name: "List"
validates :project, presence: true, if: :project_needed?
validates :group, presence: true, unless: :project
+ scope :with_associations, -> { preload(:destroyable_lists) }
+
def project_needed?
!group
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 3727a9861aa..fd5aa216174 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -295,6 +295,11 @@ module Ci
end
end
+ def self.latest_for_shas(shas)
+ max_id_per_sha = for_sha(shas).group(:sha).select("max(id)")
+ where(id: max_id_per_sha)
+ end
+
def self.latest_successful_ids_per_project
success.group(:project_id).select('max(id) as id')
end
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 4aaa1f941e5..9e4b87d0993 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -40,12 +40,6 @@ module Clusters
content_values.to_yaml
end
- # Will be addressed in future MRs
- # We need to investigate and document what will be permanently deleted.
- def allowed_to_uninstall?
- false
- end
-
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
diff --git a/app/models/concerns/deployable.rb b/app/models/concerns/deployable.rb
index bc12b06b5af..957b72f3721 100644
--- a/app/models/concerns/deployable.rb
+++ b/app/models/concerns/deployable.rb
@@ -18,6 +18,7 @@ module Deployable
return unless environment.persisted?
create_deployment!(
+ cluster_id: environment.deployment_platform&.cluster_id,
project_id: environment.project_id,
environment: environment,
ref: ref,
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index 46d2c345758..22b6b1d720c 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -25,7 +25,7 @@ module RelativePositioning
relative_position = position_between(max_relative_position, MAX_POSITION)
object.relative_position = relative_position
max_relative_position = relative_position
- object.save
+ object.save(touch: false)
end
end
end
@@ -159,7 +159,7 @@ module RelativePositioning
def save_positionable_neighbours
return unless @positionable_neighbours
- status = @positionable_neighbours.all?(&:save)
+ status = @positionable_neighbours.all? { |issue| issue.save(touch: false) }
@positionable_neighbours = nil
status
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index f0fa5974787..a8f5642f726 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -7,6 +7,7 @@ class Deployment < ApplicationRecord
belongs_to :project, required: true
belongs_to :environment, required: true
+ belongs_to :cluster, class_name: 'Clusters::Cluster', optional: true
belongs_to :user
belongs_to :deployable, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
@@ -196,7 +197,22 @@ class Deployment < ApplicationRecord
private
def prometheus_adapter
- environment.prometheus_adapter
+ service = project.find_or_initialize_service('prometheus')
+
+ if service.can_query?
+ service
+ else
+ cluster_prometheus
+ end
+ end
+
+ # TODO remove fallback case to deployment_platform_cluster.
+ # Otherwise we will continue to pay the performance penalty described in
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/63475
+ def cluster_prometheus
+ cluster_with_fallback = cluster || deployment_platform_cluster
+
+ cluster_with_fallback.application_prometheus if cluster_with_fallback&.application_prometheus_available?
end
def ref_path
diff --git a/app/models/list.rb b/app/models/list.rb
index 17b1a8510cf..d28a9bda82d 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -16,6 +16,7 @@ class List < ApplicationRecord
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
scope :preload_associations, -> { preload(:board, :label) }
+ scope :ordered, -> { order(:list_type, :position) }
class << self
def destroyable_types
diff --git a/app/models/note.rb b/app/models/note.rb
index b55af7d9b5e..4e9ea146485 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -452,7 +452,7 @@ class Note < ApplicationRecord
noteable_object&.touch
- # We return the noteable object so we can re-use it in EE for ElasticSearch.
+ # We return the noteable object so we can re-use it in EE for Elasticsearch.
noteable_object
end
diff --git a/app/models/project.rb b/app/models/project.rb
index b102e0580e7..0f4fba5d0b6 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1445,11 +1445,6 @@ class Project < ApplicationRecord
end
def in_fork_network_of?(other_project)
- # TODO: Remove this in a next release when all fork_networks are populated
- # This makes sure all MergeRequests remain valid while the projects don't
- # have a fork_network yet.
- return true if forked_from?(other_project)
-
return false if fork_network.nil? || other_project.fork_network.nil?
fork_network == other_project.fork_network
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 7b4832b84a8..f31eb7fd19a 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -14,8 +14,8 @@ class JiraService < IssueTrackerService
format: { with: Gitlab::Regex.jira_transition_id_regex, message: s_("JiraService|transition ids can have only numbers which can be split with , or ;") },
allow_blank: true
- # JIRA cloud version is deprecating authentication via username and password.
- # We should use username/password for JIRA server and email/api_token for JIRA cloud,
+ # Jira Cloud version is deprecating authentication via username and password.
+ # We should use username/password for Jira Server and email/api_token for Jira Cloud,
# for more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/49936.
prop_accessor :username, :password, :url, :api_url, :jira_issue_transition_id, :title, :description
@@ -24,7 +24,7 @@ class JiraService < IssueTrackerService
alias_method :project_url, :url
# When these are false GitLab does not create cross reference
- # comments on JIRA except when an issue gets transitioned.
+ # comments on Jira except when an issue gets transitioned.
def self.supported_events
%w(commit merge_request)
end
@@ -69,16 +69,16 @@ class JiraService < IssueTrackerService
end
def help
- "You need to configure JIRA before enabling this service. For more details
+ "You need to configure Jira before enabling this service. For more details
read the
- [JIRA service documentation](#{help_page_url('user/project/integrations/jira')})."
+ [Jira service documentation](#{help_page_url('user/project/integrations/jira')})."
end
def title
if self.properties && self.properties['title'].present?
self.properties['title']
else
- 'JIRA'
+ 'Jira'
end
end
@@ -97,7 +97,7 @@ class JiraService < IssueTrackerService
def fields
[
{ type: 'text', name: 'url', title: s_('JiraService|Web URL'), placeholder: 'https://jira.example.com', required: true },
- { type: 'text', name: 'api_url', title: s_('JiraService|JIRA API URL'), placeholder: s_('JiraService|If different from Web URL') },
+ { type: 'text', name: 'api_url', title: s_('JiraService|Jira API URL'), placeholder: s_('JiraService|If different from Web URL') },
{ type: 'text', name: 'username', title: s_('JiraService|Username or Email'), placeholder: s_('JiraService|Use a username for server version and an email for cloud version'), required: true },
{ type: 'password', name: 'password', title: s_('JiraService|Password or API token'), placeholder: s_('JiraService|Use a password for server version and an API token for cloud version'), required: true },
{ type: 'text', name: 'jira_issue_transition_id', title: s_('JiraService|Transition ID(s)'), placeholder: s_('JiraService|Use , or ; to separate multiple transition IDs') }
@@ -130,7 +130,7 @@ class JiraService < IssueTrackerService
commit_url = build_entity_url(:commit, commit_id)
- # Depending on the JIRA project's workflow, a comment during transition
+ # Depending on the Jira project's workflow, a comment during transition
# may or may not be allowed. Refresh the issue after transition and check
# if it is closed, so we don't have one comment for every commit.
issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue)
@@ -177,7 +177,7 @@ class JiraService < IssueTrackerService
{ success: success, result: result }
end
- # JIRA does not need test data.
+ # Jira does not need test data.
# We are requesting the project that belongs to the project key.
def test_data(user = nil, project = nil)
nil
@@ -313,7 +313,7 @@ class JiraService < IssueTrackerService
name == "project_snippet" ? "snippet" : name
end
- # Handle errors when doing JIRA API calls
+ # Handle errors when doing Jira API calls
def jira_request
yield
@@ -339,9 +339,9 @@ class JiraService < IssueTrackerService
def self.event_description(event)
case event
when "merge_request", "merge_request_events"
- s_("JiraService|JIRA comments will be created when an issue gets referenced in a merge request.")
+ s_("JiraService|Jira comments will be created when an issue gets referenced in a merge request.")
when "commit", "commit_events"
- s_("JiraService|JIRA comments will be created when an issue gets referenced in a commit.")
+ s_("JiraService|Jira comments will be created when an issue gets referenced in a commit.")
end
end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index e05d3dd58ac..992ed7485e5 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -282,46 +282,6 @@ class Repository
ref_exists?(keep_around_ref_name(sha))
end
- def diverging_commit_counts(branch)
- return diverging_commit_counts_without_max(branch) if Feature.enabled?('gitaly_count_diverging_commits_no_max')
-
- ## TODO: deprecate the below code after 12.0
- @root_ref_hash ||= raw_repository.commit(root_ref).id
- cache.fetch(:"diverging_commit_counts_#{branch.name}") do
- # Rugged seems to throw a `ReferenceError` when given branch_names rather
- # than SHA-1 hashes
- branch_sha = branch.dereferenced_target.sha
-
- number_commits_behind, number_commits_ahead =
- raw_repository.diverging_commit_count(
- @root_ref_hash,
- branch_sha,
- max_count: MAX_DIVERGING_COUNT)
-
- if number_commits_behind + number_commits_ahead >= MAX_DIVERGING_COUNT
- { distance: MAX_DIVERGING_COUNT }
- else
- { behind: number_commits_behind, ahead: number_commits_ahead }
- end
- end
- end
-
- def diverging_commit_counts_without_max(branch)
- @root_ref_hash ||= raw_repository.commit(root_ref).id
- cache.fetch(:"diverging_commit_counts_without_max_#{branch.name}") do
- # Rugged seems to throw a `ReferenceError` when given branch_names rather
- # than SHA-1 hashes
- branch_sha = branch.dereferenced_target.sha
-
- number_commits_behind, number_commits_ahead =
- raw_repository.diverging_commit_count(
- @root_ref_hash,
- branch_sha)
-
- { behind: number_commits_behind, ahead: number_commits_ahead }
- end
- end
-
def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:, path: nil)
raw_repository.archive_metadata(
ref,
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index f4fdac2558c..00931457344 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -194,6 +194,10 @@ class Snippet < ApplicationRecord
'snippet'
end
+ def to_ability_name
+ model_name.singular
+ end
+
class << self
# Searches for snippets with a matching title or file name.
#
diff --git a/app/policies/award_emoji_policy.rb b/app/policies/award_emoji_policy.rb
new file mode 100644
index 00000000000..21e382e24b3
--- /dev/null
+++ b/app/policies/award_emoji_policy.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AwardEmojiPolicy < BasePolicy
+ delegate { @subject.awardable if DeclarativePolicy.has_policy?(@subject.awardable) }
+
+ condition(:can_read_awardable) do
+ can?(:"read_#{@subject.awardable.to_ability_name}")
+ end
+
+ rule { can_read_awardable }.enable :read_emoji
+end
diff --git a/app/presenters/award_emoji_presenter.rb b/app/presenters/award_emoji_presenter.rb
new file mode 100644
index 00000000000..98713855d35
--- /dev/null
+++ b/app/presenters/award_emoji_presenter.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class AwardEmojiPresenter < Gitlab::View::Presenter::Delegated
+ presents :award_emoji
+
+ def description
+ as_emoji['description']
+ end
+
+ def unicode
+ as_emoji['unicode']
+ end
+
+ def emoji
+ as_emoji['moji']
+ end
+
+ def unicode_version
+ Gitlab::Emoji.emoji_unicode_version(award_emoji.name)
+ end
+
+ private
+
+ def as_emoji
+ @emoji ||= Gitlab::Emoji.emojis[award_emoji.name] || {}
+ end
+end
diff --git a/app/presenters/commit_presenter.rb b/app/presenters/commit_presenter.rb
index 05adbe1d4f5..fc9853733c1 100644
--- a/app/presenters/commit_presenter.rb
+++ b/app/presenters/commit_presenter.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
-class CommitPresenter < Gitlab::View::Presenter::Simple
+class CommitPresenter < Gitlab::View::Presenter::Delegated
+ include GlobalID::Identification
+
presents :commit
def status_for(ref)
@@ -10,4 +12,8 @@ class CommitPresenter < Gitlab::View::Presenter::Simple
def any_pipelines?
can?(current_user, :read_pipeline, commit.project) && commit.pipelines.any?
end
+
+ def web_url
+ Gitlab::UrlBuilder.new(commit).url
+ end
end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 43aced598a9..fd2673fa0cc 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -217,8 +217,12 @@ class MergeRequestWidgetEntity < IssuableEntity
project_merge_request_path(merge_request.project, merge_request, format: :diff)
end
- expose :status_path do |merge_request|
- project_merge_request_path(merge_request.target_project, merge_request, format: :json)
+ expose :merge_request_basic_path do |merge_request|
+ project_merge_request_path(merge_request.target_project, merge_request, serializer: :basic, format: :json)
+ end
+
+ expose :merge_request_widget_path do |merge_request|
+ widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
expose :ci_environments_status_path do |merge_request|
diff --git a/app/services/branches/diverging_commit_counts_service.rb b/app/services/branches/diverging_commit_counts_service.rb
new file mode 100644
index 00000000000..f947cec1663
--- /dev/null
+++ b/app/services/branches/diverging_commit_counts_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Branches
+ class DivergingCommitCountsService
+ def initialize(repository)
+ @repository = repository
+ @cache = Gitlab::RepositoryCache.new(repository)
+ end
+
+ def call(branch)
+ if Feature.enabled?('gitaly_count_diverging_commits_no_max')
+ diverging_commit_counts_without_max(branch)
+ else
+ diverging_commit_counts(branch)
+ end
+ end
+
+ private
+
+ attr_reader :repository, :cache
+
+ delegate :raw_repository, to: :repository
+
+ def diverging_commit_counts(branch)
+ ## TODO: deprecate the below code after 12.0
+ @root_ref_hash ||= raw_repository.commit(repository.root_ref).id
+ cache.fetch(:"diverging_commit_counts_#{branch.name}") do
+ number_commits_behind, number_commits_ahead =
+ repository.raw_repository.diverging_commit_count(
+ @root_ref_hash,
+ branch.dereferenced_target.sha,
+ max_count: Repository::MAX_DIVERGING_COUNT)
+
+ if number_commits_behind + number_commits_ahead >= Repository::MAX_DIVERGING_COUNT
+ { distance: Repository::MAX_DIVERGING_COUNT }
+ else
+ { behind: number_commits_behind, ahead: number_commits_ahead }
+ end
+ end
+ end
+
+ def diverging_commit_counts_without_max(branch)
+ @root_ref_hash ||= raw_repository.commit(repository.root_ref).id
+ cache.fetch(:"diverging_commit_counts_without_max_#{branch.name}") do
+ number_commits_behind, number_commits_ahead =
+ raw_repository.diverging_commit_count(
+ @root_ref_hash,
+ branch.dereferenced_target.sha)
+
+ { behind: number_commits_behind, ahead: number_commits_ahead }
+ end
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 26132f1824a..02de080e0ba 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -205,7 +205,7 @@ class IssuableBaseService < BaseService
end
if issuable.changed? || params.present?
- issuable.assign_attributes(params.merge(updated_by: current_user))
+ issuable.assign_attributes(params)
if has_title_or_description_changed?(issuable)
issuable.assign_attributes(last_edited_at: Time.now, last_edited_by: current_user)
@@ -213,11 +213,16 @@ class IssuableBaseService < BaseService
before_update(issuable)
+ # Do not touch when saving the issuable if only changes position within a list. We should call
+ # this method at this point to capture all possible changes.
+ should_touch = update_timestamp?(issuable)
+
+ issuable.updated_by = current_user if should_touch
# We have to perform this check before saving the issuable as Rails resets
# the changed fields upon calling #save.
update_project_counters = issuable.project && update_project_counter_caches?(issuable)
- if issuable.with_transaction_returning_status { issuable.save }
+ if issuable.with_transaction_returning_status { issuable.save(touch: should_touch) }
# We do not touch as it will affect a update on updated_at field
ActiveRecord::Base.no_touching do
Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels])
@@ -402,4 +407,8 @@ class IssuableBaseService < BaseService
def ensure_milestone_available(issuable)
issuable.milestone_id = nil unless issuable.milestone_available?
end
+
+ def update_timestamp?(issuable)
+ issuable.changes.keys != ["relative_position"]
+ end
end
diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb
index e69791872cc..2a217a6f689 100644
--- a/app/services/merge_requests/create_from_issue_service.rb
+++ b/app/services/merge_requests/create_from_issue_service.rb
@@ -6,17 +6,20 @@ module MergeRequests
# branch - the name of new branch
# ref - the source of new branch.
- @branch_name = params[:branch_name]
- @issue_iid = params[:issue_iid]
- @ref = params[:ref]
+ @branch_name = params[:branch_name]
+ @issue_iid = params[:issue_iid]
+ @ref = params[:ref]
+ @target_project_id = params[:target_project_id]
super(project, user)
end
def execute
+ return error('Project not found') if target_project.blank?
+ return error('Not allowed to create merge request') unless can_create_merge_request?
return error('Invalid issue iid') unless @issue_iid.present? && issue.present?
- result = CreateBranchService.new(project, current_user).execute(branch_name, ref)
+ result = CreateBranchService.new(target_project, current_user).execute(branch_name, ref)
return result if result[:status] == :error
new_merge_request = create(merge_request)
@@ -26,7 +29,7 @@ module MergeRequests
success(new_merge_request)
else
- SystemNoteService.new_issue_branch(issue, project, current_user, branch_name)
+ SystemNoteService.new_issue_branch(issue, project, current_user, branch_name, branch_project: target_project)
error(new_merge_request.errors)
end
@@ -34,6 +37,10 @@ module MergeRequests
private
+ def can_create_merge_request?
+ can?(current_user, :create_merge_request_from, target_project)
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def issue
@issue ||= IssuesFinder.new(current_user, project_id: project.id).find_by(iid: @issue_iid)
@@ -45,21 +52,21 @@ module MergeRequests
end
def ref
- return @ref if project.repository.branch_exists?(@ref)
+ return @ref if target_project.repository.branch_exists?(@ref)
- project.default_branch || 'master'
+ target_project.default_branch || 'master'
end
def merge_request
- MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
+ MergeRequests::BuildService.new(target_project, current_user, merge_request_params).execute
end
def merge_request_params
{
issue_iid: @issue_iid,
- source_project_id: project.id,
+ source_project_id: target_project.id,
source_branch: branch_name,
- target_project_id: project.id,
+ target_project_id: target_project.id,
target_branch: ref
}
end
@@ -67,5 +74,14 @@ module MergeRequests
def success(merge_request)
super().merge(merge_request: merge_request)
end
+
+ def target_project
+ @target_project ||=
+ if @target_project_id.present?
+ project.forks.find_by_id(@target_project_id)
+ else
+ project
+ end
+ end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 1390f7cdf46..8f7cfe582ca 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -404,8 +404,9 @@ module SystemNoteService
# Example note text:
#
# "created branch `201-issue-branch-button`"
- def new_issue_branch(issue, project, author, branch)
- link = url_helpers.project_compare_path(project, from: project.default_branch, to: branch)
+ def new_issue_branch(issue, project, author, branch, branch_project: nil)
+ branch_project ||= project
+ link = url_helpers.project_compare_path(branch_project, from: branch_project.default_branch, to: branch)
body = "created branch [`#{branch}`](#{link}) to address this issue"
@@ -413,7 +414,7 @@ module SystemNoteService
end
def new_merge_request(issue, project, author, merge_request)
- body = "created merge request #{merge_request.to_reference} to address this issue"
+ body = "created merge request #{merge_request.to_reference(project)} to address this issue"
create_note(NoteSummary.new(issue, project, author, body, action: 'merge'))
end
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
index 15c13a452ad..8f52e9cb23f 100644
--- a/app/services/users/update_service.rb
+++ b/app/services/users/update_service.rb
@@ -63,12 +63,20 @@ module Users
def assign_identity
return unless identity_params.present?
- identity = user.identities.find_or_create_by(provider: identity_params[:provider]) # rubocop: disable CodeReuse/ActiveRecord
+ identity = user.identities.find_or_create_by(provider_params) # rubocop: disable CodeReuse/ActiveRecord
identity.update(identity_params)
end
def identity_attributes
[:provider, :extern_uid]
end
+
+ def provider_attributes
+ [:provider]
+ end
+
+ def provider_params
+ identity_params.slice(*provider_attributes)
+ end
end
end
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index e401488ecff..a9af5ba5008 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -347,7 +347,7 @@
= _('Settings')
%li.divider.fly-out-top-item
= nav_link(path: %w[projects#edit]) do
- = link_to edit_project_path(@project), title: _('General') do
+ = link_to edit_project_path(@project), title: _('General'), class: 'qa-general-settings-link' do
%span
= _('General')
= nav_link(controller: :project_members) do
diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb
index c3f2902c78a..6c0d7b1e60b 100644
--- a/app/views/notify/new_merge_request_email.text.erb
+++ b/app/views/notify/new_merge_request_email.text.erb
@@ -1,7 +1,7 @@
<%= @merge_request.author_name %> <%= 'created a merge request:' %> <%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
-<%= 'Author' %>: <%= @merge_request.author_name %>
+<%= 'Author:' %> <%= @merge_request.author_name %>
<%= assignees_label(@merge_request) %>
<%= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter %>
diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml
index 1e27c71d20d..b6689f4b57a 100644
--- a/app/views/projects/_commit_button.html.haml
+++ b/app/views/projects/_commit_button.html.haml
@@ -1,5 +1,5 @@
.form-actions
- = button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-success'
+ = button_tag 'Commit changes', class: 'btn commit-btn js-commit-button btn-success qa-commit-button'
= link_to 'Cancel', cancel_path,
class: 'btn btn-cancel', data: {confirm: leave_edit_message}
diff --git a/app/views/projects/_files.html.haml b/app/views/projects/_files.html.haml
index 2b0c3985755..6763513f9ae 100644
--- a/app/views/projects/_files.html.haml
+++ b/app/views/projects/_files.html.haml
@@ -9,7 +9,9 @@
.nav-block
= render 'projects/tree/tree_header', tree: @tree
- - if commit
+ - if vue_file_list_enabled?
+ #js-last-commit
+ - elsif commit
= render 'shared/commit_well', commit: commit, ref: ref, project: project
- if is_project_overview
diff --git a/app/views/projects/_merge_request_settings_description_text.html.haml b/app/views/projects/_merge_request_settings_description_text.html.haml
new file mode 100644
index 00000000000..42964c900b3
--- /dev/null
+++ b/app/views/projects/_merge_request_settings_description_text.html.haml
@@ -0,0 +1 @@
+%p= s_('ProjectSettings|Choose your merge method, merge options, and merge checks.')
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index a54460f1196..283b845e40d 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -32,7 +32,7 @@
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2', tabindex: '-1'
.file-editor.code
- %pre.js-edit-mode-pane#editor= params[:content] || local_assigns[:blob_data]
+ %pre.js-edit-mode-pane.qa-editor#editor= params[:content] || local_assigns[:blob_data]
- if local_assigns[:path]
.js-edit-mode-pane#preview.hide
.center
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 3638334d61c..dbff2115f50 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,11 +1,7 @@
- merged = local_assigns.fetch(:merged, false)
- commit = @repository.commit(branch.dereferenced_target)
-- diverging_commit_counts = @repository.diverging_commit_counts(branch)
-- number_commits_distance = diverging_commit_counts[:distance]
-- number_commits_behind = diverging_commit_counts[:behind]
-- number_commits_ahead = diverging_commit_counts[:ahead]
- merge_project = merge_request_source_project_for_project(@project)
-%li{ class: "branch-item js-branch-#{branch.name}" }
+%li{ class: "branch-item js-branch-item js-branch-#{branch.name}", data: { name: branch.name } }
.branch-info
.branch-title
= sprite_icon('fork', size: 12)
@@ -30,7 +26,7 @@
= s_('Branches|Cant find HEAD commit for this branch')
- if branch.name != @repository.root_ref
- .js-branch-divergence-graph{ data: { distance: number_commits_distance, ahead_count: number_commits_ahead, behind_count: number_commits_behind, default_branch: @repository.root_ref, max_commits: @max_commits } }
+ .js-branch-divergence-graph
.controls.d-none.d-md-block<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name)
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index d270e461ac8..11340d12423 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -47,6 +47,7 @@
= render_if_exists 'projects/commits/mirror_status'
+ .js-branch-list{ data: { diverging_counts_endpoint: diverging_commit_counts_namespace_project_branches_path(@project.namespace, @project, format: :json) } }
- if can?(current_user, :admin_project, @project)
- project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project)
.row-content-block
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index c15b84d0aac..3403564992e 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -27,7 +27,7 @@
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
- %p= _('Choose your merge method, options, checks, and set up a default merge request description template.')
+ = render_if_exists 'projects/merge_request_settings_description_text'
.settings-content
= render_if_exists 'shared/promotions/promote_mr_features'
@@ -121,7 +121,7 @@
%li= _('You can only transfer the project to namespaces you manage.')
%li= _('You will need to update your local repositories to point to the new location.')
%li= _('Project visibility level will be changed to match namespace rules when transferring to a group.')
- = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => transfer_project_message(@project) }
+ = f.submit 'Transfer project', class: "btn btn-remove js-confirm-danger qa-transfer-button", data: { "confirm-danger-message" => transfer_project_message(@project) }
- if @project.forked? && can?(current_user, :remove_fork_project, @project)
.sub-section
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index d59b2d4fb01..c13a47b0b09 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -31,21 +31,19 @@
= button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do
= s_('Environments|Stop environment')
- .row.top-area.adjust
- .col-md-7
- %h3.page-title= @environment.name
- .col-md-5
- .nav-controls
- = render 'projects/environments/terminal_button', environment: @environment
- = render 'projects/environments/external_url', environment: @environment
- = render 'projects/environments/metrics_button', environment: @environment
- - if can?(current_user, :update_environment, @environment)
- = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
- - if can?(current_user, :stop_environment, @environment)
- = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
- target: '#stop-environment-modal' } do
- = sprite_icon('stop')
- = s_('Environments|Stop')
+ .top-area
+ %h3.page-title= @environment.name
+ .nav-controls.ml-auto.my-2
+ = render 'projects/environments/terminal_button', environment: @environment
+ = render 'projects/environments/external_url', environment: @environment
+ = render 'projects/environments/metrics_button', environment: @environment
+ - if can?(current_user, :update_environment, @environment)
+ = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
+ - if can?(current_user, :stop_environment, @environment)
+ = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal',
+ target: '#stop-environment-modal' } do
+ = sprite_icon('stop')
+ = s_('Environments|Stop')
.environments-container
- if @deployments.blank?
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index ea6349f2f57..1d0bc588c9c 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -76,6 +76,7 @@
#{ _('New tag') }
.tree-controls
+ = render_if_exists 'projects/tree/lock_link'
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/find_file_link'
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index df408e5fb60..ee7d89a9bd8 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -87,4 +87,5 @@
= _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
+ = render_if_exists 'search/category_elasticsearch'
= users
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 3967c8148d2..8e3b482e27d 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -1,4 +1,4 @@
-#modal-confirm-danger.modal{ tabindex: -1 }
+#modal-confirm-danger.modal.qa-confirm-modal{ tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
@@ -17,6 +17,6 @@
to proceed or close this modal to cancel.
.form-group
- = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
+ = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input qa-confirm-input'
.form-actions
- = submit_tag _('Confirm'), class: "btn btn-danger js-confirm-danger-submit"
+ = submit_tag _('Confirm'), class: "btn btn-danger js-confirm-danger-submit qa-confirm-button"
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index f9cfcabc015..6c0613605eb 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -1,52 +1,62 @@
.board.d-inline-block.h-100.px-2.align-top.ws-normal{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
":data-id" => "list.id" }
.board-inner.d-flex.flex-column.position-relative.h-100.rounded
- %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color, "position-relative": list.isExpanded, "position-absolute position-top-0 position-left-0 w-100 h-100": !list.isExpanded }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", "@click" => "toggleExpanded($event)" }
- %h3.board-title.m-0.d-flex.align-items-center.py-2.px-3.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset), "p-0 border-bottom-0 justify-content-center": !list.isExpanded }' }
- %i.fa.fa-fw.board-title-expandable-toggle{ "v-if": "list.isExpandable",
- ":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded }",
- "aria-hidden": "true" }
+ %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color, "position-relative": list.isExpanded, "position-absolute position-top-0 position-left-0 w-100 h-100": !list.isExpanded }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }" }
+ %h3.board-title.m-0.d-flex.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset), "border-bottom-0": !list.isExpanded }' }
+
+ .board-title-caret.no-drag{ "v-if": "list.isExpandable",
+ "aria-hidden": "true",
+ ":aria-label": "caretTooltip",
+ ":title": "caretTooltip",
+ "v-tooltip": "",
+ data: { placement: "bottom" },
+ "@click": "toggleExpanded" }
+ %i.fa.fa-fw{ ":class": '{ "fa-caret-right": list.isExpanded, "fa-caret-down": !list.isExpanded }' }
= render_if_exists "shared/boards/components/list_milestone"
%a.user-avatar-link.js-no-trigger{ "v-if": "list.type === \"assignee\"", ":href": "list.assignee.path" }
-# haml-lint:disable AltText
%img.avatar.s20.has-tooltip{ height: "20", width: "20", ":src": "list.assignee.avatar", ":alt": "list.assignee.name" }
- %span.board-title-text.has-tooltip.block-truncated{ "v-if": "list.type !== \"label\"",
- ":title" => '((list.label && list.label.description) || list.title || "")', data: { container: "body" } }
- {{ list.title }}
+ .board-title-text
+ %span.board-title-main-text.block-truncated{ "v-if": "list.type !== \"label\"",
+ ":title" => '((list.label && list.label.description) || list.title || "")',
+ data: { container: "body" },
+ ":class": "{ 'has-tooltip': !['backlog', 'closed'].includes(list.type) }" }
+ {{ list.title }}
- %span.board-title-sub-text.prepend-left-5.has-tooltip{ "v-if": "list.type === \"assignee\"",
- ":title" => '(list.assignee && list.assignee.username || "")' }
- @{{ list.assignee.username }}
+ %span.board-title-sub-text.prepend-left-5.has-tooltip{ "v-if": "list.type === \"assignee\"",
+ ":title" => '(list.assignee && list.assignee.username || "")' }
+ @{{ list.assignee.username }}
- %span.has-tooltip{ "v-if": "list.type === \"label\"",
- ":title" => '(list.label ? list.label.description : "")',
- data: { container: "body", placement: "bottom" },
- class: "badge color-label title board-title-text",
- ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.textColor ? list.label.textColor : \"#2e2e2e\") }" }
- {{ list.title }}
+ %span.has-tooltip.badge.color-label.title{ "v-if": "list.type === \"label\"",
+ ":title" => '(list.label ? list.label.description : "")',
+ data: { container: "body", placement: "bottom" },
+ ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.textColor ? list.label.textColor : \"#2e2e2e\") }" }
+ {{ list.title }}
- if can?(current_user, :admin_list, current_board_parent)
%board-delete{ "inline-template" => true,
":list" => "list",
"v-if" => "!list.preset && list.id" }
- %button.board-delete.p-0.border-0.has-tooltip.float-right{ type: "button", title: _("Delete list"), ":class": "{ 'd-none': !list.isExpanded }", "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
+ %button.board-delete.no-drag.p-0.border-0.has-tooltip.float-right{ type: "button", title: _("Delete list"), ":class": "{ 'd-none': !list.isExpanded }", "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash")
- .issue-count-badge.text-secondary{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"', ":title": "counterTooltip", ":class": "{ 'd-none': !list.isExpanded }", "v-tooltip": true, data: { placement: "top" } }
- %span.issue-count-badge-count
- %icon.mr-1{ name: "issues" }
- {{ list.issuesSize }}
- = render_if_exists "shared/boards/components/list_weight"
- %button.issue-count-badge-add-button.btn.btn-sm.btn-default.ml-1.has-tooltip.js-no-trigger-collapse{ type: "button",
+ .issue-count-badge.no-drag.text-secondary{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"', ":title": "counterTooltip", "v-tooltip": true, data: { placement: "top" } }
+ %span.d-inline-flex
+ %span.issue-count-badge-count
+ %icon.mr-1{ name: "issues" }
+ {{ list.issuesSize }}
+ = render_if_exists "shared/boards/components/list_weight"
+
+ %button.issue-count-badge-add-button.no-drag.btn.btn-sm.btn-default.ml-1.has-tooltip{ type: "button",
"@click" => "showNewIssueForm",
"v-if" => "isNewIssueShown",
":class": "{ 'd-none': !list.isExpanded }",
"aria-label" => _("New issue"),
"title" => _("New issue"),
data: { placement: "top", container: "body" } }
- = icon("plus", class: "js-no-trigger-collapse")
+ = icon("plus")
%board-list{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"',
":list" => "list",
diff --git a/changelogs/unreleased/11039-moved-code-difference-from-EE-to-CE.yml b/changelogs/unreleased/11039-moved-code-difference-from-EE-to-CE.yml
new file mode 100644
index 00000000000..10c5eed9556
--- /dev/null
+++ b/changelogs/unreleased/11039-moved-code-difference-from-EE-to-CE.yml
@@ -0,0 +1,5 @@
+---
+title: "Moved EE/CE code differences for file `app/views/search/_category.html.haml` into CE"
+merge_request: 28755
+author: Michel Engelen
+type: other
diff --git a/changelogs/unreleased/44106-include-subgroups-in-group-activity.yml b/changelogs/unreleased/44106-include-subgroups-in-group-activity.yml
new file mode 100644
index 00000000000..6e78d61ebc4
--- /dev/null
+++ b/changelogs/unreleased/44106-include-subgroups-in-group-activity.yml
@@ -0,0 +1,5 @@
+---
+title: Include events from subgroups in group's activity
+merge_request: 29953
+author: Fabian Schneider @fabsrc
+type: changed
diff --git a/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml b/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml
new file mode 100644
index 00000000000..efc6af7845c
--- /dev/null
+++ b/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml
@@ -0,0 +1,5 @@
+---
+title: Will not update issue timestamps when changing positions in a list
+merge_request: 29677
+author:
+type: changed
diff --git a/changelogs/unreleased/45120-fix-ide-editor-to-update-size-on-show-change.yml b/changelogs/unreleased/45120-fix-ide-editor-to-update-size-on-show-change.yml
new file mode 100644
index 00000000000..592612c2615
--- /dev/null
+++ b/changelogs/unreleased/45120-fix-ide-editor-to-update-size-on-show-change.yml
@@ -0,0 +1,5 @@
+---
+title: Fix IDE editor not showing when switching back from preview
+merge_request: 30135
+author:
+type: fixed
diff --git a/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml b/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml
new file mode 100644
index 00000000000..bf6f314f0ce
--- /dev/null
+++ b/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml
@@ -0,0 +1,6 @@
+---
+title: Improve discussion reply buttons layout and how jump to next discussion button
+ appears
+merge_request: 29779
+author:
+type: changed
diff --git a/changelogs/unreleased/62826-graphql-emoji-mutations.yml b/changelogs/unreleased/62826-graphql-emoji-mutations.yml
new file mode 100644
index 00000000000..0c0aaedf844
--- /dev/null
+++ b/changelogs/unreleased/62826-graphql-emoji-mutations.yml
@@ -0,0 +1,5 @@
+---
+title: GraphQL mutations for add, remove and toggle emoji
+merge_request: 29919
+author:
+type: added
diff --git a/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml b/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml
new file mode 100644
index 00000000000..749fe6a9cb0
--- /dev/null
+++ b/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Environment details header border misaligned
+merge_request: 30011
+author:
+type: fixed
diff --git a/changelogs/unreleased/63479-jira-capitalization.yml b/changelogs/unreleased/63479-jira-capitalization.yml
new file mode 100644
index 00000000000..a4cc32beba6
--- /dev/null
+++ b/changelogs/unreleased/63479-jira-capitalization.yml
@@ -0,0 +1,5 @@
+---
+title: Replace 'JIRA' with 'Jira'
+merge_request: 29849
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/63590-pipeline-actions-cause-full-refresh.yml b/changelogs/unreleased/63590-pipeline-actions-cause-full-refresh.yml
new file mode 100644
index 00000000000..a1e7d4679d8
--- /dev/null
+++ b/changelogs/unreleased/63590-pipeline-actions-cause-full-refresh.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pipelines table to update without refreshing after action
+merge_request: 30190
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-clusters-to-deployment.yml b/changelogs/unreleased/add-clusters-to-deployment.yml
new file mode 100644
index 00000000000..c85bd3635cc
--- /dev/null
+++ b/changelogs/unreleased/add-clusters-to-deployment.yml
@@ -0,0 +1,5 @@
+---
+title: Persist the cluster a deployment was deployed to
+merge_request: 29960
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-strategies-column-to-scopes-table.yml b/changelogs/unreleased/add-strategies-column-to-scopes-table.yml
new file mode 100644
index 00000000000..0bb87fca014
--- /dev/null
+++ b/changelogs/unreleased/add-strategies-column-to-scopes-table.yml
@@ -0,0 +1,5 @@
+---
+title: Add strategies column to operations_feature_flag_scopes table
+merge_request: 29808
+author:
+type: other
diff --git a/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml b/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml
new file mode 100644
index 00000000000..9f6a2040095
--- /dev/null
+++ b/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml
@@ -0,0 +1,5 @@
+---
+title: Update merge requests section description text on project settings page
+merge_request: 27838
+author:
+type: changed \ No newline at end of file
diff --git a/changelogs/unreleased/check-min-schema-migrate.yml b/changelogs/unreleased/check-min-schema-migrate.yml
new file mode 100644
index 00000000000..d0f4ae1f5d7
--- /dev/null
+++ b/changelogs/unreleased/check-min-schema-migrate.yml
@@ -0,0 +1,5 @@
+---
+title: Added a min schema version check to db:migrate
+merge_request: 29882
+author:
+type: added
diff --git a/changelogs/unreleased/feature-uninstall_jupyter_hub_app.yml b/changelogs/unreleased/feature-uninstall_jupyter_hub_app.yml
new file mode 100644
index 00000000000..28753aa719c
--- /dev/null
+++ b/changelogs/unreleased/feature-uninstall_jupyter_hub_app.yml
@@ -0,0 +1,5 @@
+---
+title: Allow JupyterHub to be uninstalled from the UI
+merge_request: 30097
+author:
+type: added
diff --git a/changelogs/unreleased/graphql-tree-last-commit.yml b/changelogs/unreleased/graphql-tree-last-commit.yml
new file mode 100644
index 00000000000..5104ca6687e
--- /dev/null
+++ b/changelogs/unreleased/graphql-tree-last-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Added commit type to tree GraphQL response
+merge_request: 29412
+author:
+type: added
diff --git a/changelogs/unreleased/id-extract-widget-into-different-request.yml b/changelogs/unreleased/id-extract-widget-into-different-request.yml
new file mode 100644
index 00000000000..3b9f5fdd6bd
--- /dev/null
+++ b/changelogs/unreleased/id-extract-widget-into-different-request.yml
@@ -0,0 +1,5 @@
+---
+title: Add a separate endpoint for fetching MRs serialized as widgets
+merge_request: 29979
+author:
+type: performance
diff --git a/changelogs/unreleased/id-stale-branches.yml b/changelogs/unreleased/id-stale-branches.yml
new file mode 100644
index 00000000000..2f35c5a12c9
--- /dev/null
+++ b/changelogs/unreleased/id-stale-branches.yml
@@ -0,0 +1,5 @@
+---
+title: Add endpoint for fetching diverging commit counts
+merge_request: 29802
+author:
+type: performance
diff --git a/changelogs/unreleased/mh-board-tooltips.yml b/changelogs/unreleased/mh-board-tooltips.yml
new file mode 100644
index 00000000000..06fc64c52a7
--- /dev/null
+++ b/changelogs/unreleased/mh-board-tooltips.yml
@@ -0,0 +1,5 @@
+---
+title: "'Open' and 'Closed' issue board lists no longer display a redundant tooltip"
+merge_request: 30187
+author:
+type: fixed
diff --git a/changelogs/unreleased/mh-collapsible-boards.yml b/changelogs/unreleased/mh-collapsible-boards.yml
new file mode 100644
index 00000000000..b69d6e81cc4
--- /dev/null
+++ b/changelogs/unreleased/mh-collapsible-boards.yml
@@ -0,0 +1,5 @@
+---
+title: Labeled issue boards can now collapse
+merge_request: 29955
+author:
+type: added
diff --git a/changelogs/unreleased/set-higher-ttl-for-trace-write.yml b/changelogs/unreleased/set-higher-ttl-for-trace-write.yml
new file mode 100644
index 00000000000..9f17172100c
--- /dev/null
+++ b/changelogs/unreleased/set-higher-ttl-for-trace-write.yml
@@ -0,0 +1,5 @@
+---
+title: Set higher TTL for write lock of trace to prevent concurrent archiving
+merge_request: 30064
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-63910.yml b/changelogs/unreleased/sh-fix-issue-63910.yml
new file mode 100644
index 00000000000..50312558c57
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-63910.yml
@@ -0,0 +1,5 @@
+---
+title: Fix attachments using the wrong URLs in e-mails
+merge_request: 30197
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-support-subnets-ip-rate-limiter.yml b/changelogs/unreleased/sh-support-subnets-ip-rate-limiter.yml
new file mode 100644
index 00000000000..3e78c58c764
--- /dev/null
+++ b/changelogs/unreleased/sh-support-subnets-ip-rate-limiter.yml
@@ -0,0 +1,5 @@
+---
+title: Support CIDR notation in IP rate limiter
+merge_request: 30146
+author:
+type: changed
diff --git a/changelogs/unreleased/small-s-in-elasticsearch-in-code.yml b/changelogs/unreleased/small-s-in-elasticsearch-in-code.yml
new file mode 100644
index 00000000000..20d7a822cde
--- /dev/null
+++ b/changelogs/unreleased/small-s-in-elasticsearch-in-code.yml
@@ -0,0 +1,5 @@
+---
+title: Fix typo in code comments about Elasticsearch
+merge_request: 30163
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/small-s-in-elasticsearch.yml b/changelogs/unreleased/small-s-in-elasticsearch.yml
new file mode 100644
index 00000000000..7cab5c37125
--- /dev/null
+++ b/changelogs/unreleased/small-s-in-elasticsearch.yml
@@ -0,0 +1,5 @@
+---
+title: Fix typo in docs about Elasticsearch
+merge_request: 30162
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/support-jsonb-default-value.yml b/changelogs/unreleased/support-jsonb-default-value.yml
new file mode 100644
index 00000000000..d46156276f9
--- /dev/null
+++ b/changelogs/unreleased/support-jsonb-default-value.yml
@@ -0,0 +1,5 @@
+---
+title: Support jsonb default in add_column_with_default migration helper
+merge_request: 29871
+author:
+type: other
diff --git a/changelogs/unreleased/tc-rake-orphan-artifacts.yml b/changelogs/unreleased/tc-rake-orphan-artifacts.yml
new file mode 100644
index 00000000000..7081bee640a
--- /dev/null
+++ b/changelogs/unreleased/tc-rake-orphan-artifacts.yml
@@ -0,0 +1,5 @@
+---
+title: Add rake task to clean orphan artifact files
+merge_request: 29681
+author:
+type: added
diff --git a/changelogs/unreleased/transaction-metrics.yml b/changelogs/unreleased/transaction-metrics.yml
new file mode 100644
index 00000000000..8b6e9c7d9d1
--- /dev/null
+++ b/changelogs/unreleased/transaction-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Adds metrics to measure cost of expensive operations
+merge_request: 29928
+author:
+type: other
diff --git a/config/boot.rb b/config/boot.rb
index 2811f0e6188..b76b26a5e75 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -3,7 +3,7 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
# Set up gems listed in the Gemfile.
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
begin
- require 'bootsnap/setup'
+ require 'bootsnap/setup' unless ENV['DISABLE_BOOTSNAP']
rescue LoadError
# bootsnap is an optional dependency, so if we don't have it, it's fine
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index dddc5ec3540..c82d9b5ceef 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -1078,7 +1078,7 @@ test:
issues_url: "http://redmine/:project_id/:issues_tracker_id/:id"
new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new"
jira:
- title: "JIRA"
+ title: "Jira"
url: https://sample_company.atlassian.net
project_key: PROJECT
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 9e74a67b73f..c803e4615b4 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -5,6 +5,13 @@ require_relative '../object_store_settings'
Settings['ldap'] ||= Settingslogic.new({})
Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
+Gitlab.ee do
+ Settings.ldap['sync_time'] = 3600 if Settings.ldap['sync_time'].nil?
+ Settings.ldap['schedule_sync_daily'] = 1 if Settings.ldap['schedule_sync_daily'].nil?
+ Settings.ldap['schedule_sync_hour'] = 1 if Settings.ldap['schedule_sync_hour'].nil?
+ Settings.ldap['schedule_sync_minute'] = 30 if Settings.ldap['schedule_sync_minute'].nil?
+end
+
# backwards compatibility, we only have one host
if Settings.ldap['enabled'] || Rails.env.test?
if Settings.ldap['host'].present?
@@ -23,11 +30,14 @@ if Settings.ldap['enabled'] || Rails.env.test?
server['timeout'] ||= 10.seconds
server['block_auto_created_users'] = false if server['block_auto_created_users'].nil?
server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
+ server['smartcard_auth'] = false unless %w[optional required].include?(server['smartcard_auth'])
server['active_directory'] = true if server['active_directory'].nil?
server['attributes'] = {} if server['attributes'].nil?
server['lowercase_usernames'] = false if server['lowercase_usernames'].nil?
server['provider_name'] ||= "ldap#{key}".downcase
server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name'])
+ server['external_groups'] = [] if server['external_groups'].nil?
+ server['sync_ssh_keys'] = 'sshPublicKey' if server['sync_ssh_keys'].to_s == 'true'
# For backwards compatibility
server['encryption'] ||= server['method']
@@ -62,6 +72,12 @@ if Settings.ldap['enabled'] || Rails.env.test?
end
end
+Gitlab.ee do
+ Settings['smartcard'] ||= Settingslogic.new({})
+ Settings.smartcard['enabled'] = false if Settings.smartcard['enabled'].nil?
+ Settings.smartcard['client_certificate_required_port'] = 3444 if Settings.smartcard['client_certificate_required_port'].nil?
+end
+
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = true if Settings.omniauth['enabled'].nil?
Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil?
@@ -136,6 +152,7 @@ Settings['issues_tracker'] ||= {}
#
Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_project_creation'] ||= ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS
+Settings.gitlab['default_project_deletion_protection'] ||= false
Settings.gitlab['default_projects_limit'] ||= 100000
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
@@ -186,6 +203,21 @@ Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config'
Settings.gitlab['impersonation_enabled'] ||= true if Settings.gitlab['impersonation_enabled'].nil?
Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil?
+Gitlab.ee do
+ Settings.gitlab['mirror_max_delay'] ||= 300
+ Settings.gitlab['mirror_max_capacity'] ||= 30
+ Settings.gitlab['mirror_capacity_threshold'] ||= 15
+end
+
+#
+# Elasticseacrh
+#
+Gitlab.ee do
+ Settings['elasticsearch'] ||= Settingslogic.new({})
+ Settings.elasticsearch['enabled'] = false if Settings.elasticsearch['enabled'].nil?
+ Settings.elasticsearch['url'] = ENV['ELASTIC_URL'] || "http://localhost:9200"
+end
+
#
# CI
#
@@ -255,6 +287,15 @@ Settings.pages['admin'] ||= Settingslogic.new({})
Settings.pages.admin['certificate'] ||= ''
#
+# Geo
+#
+Gitlab.ee do
+ Settings['geo'] ||= Settingslogic.new({})
+ # For backwards compatibility, default to gitlab_url and if so, ensure it ends with "/"
+ Settings.geo['node_name'] = Settings.geo['node_name'].presence || Settings.gitlab['url'].chomp('/').concat('/')
+end
+
+#
# External merge request diffs
#
Settings['external_diffs'] ||= Settingslogic.new({})
@@ -281,6 +322,32 @@ Settings.uploads['object_store'] = ObjectStoreSettings.parse(Settings.uploads['o
Settings.uploads['object_store']['remote_directory'] ||= 'uploads'
#
+# Packages
+#
+Gitlab.ee do
+ Settings['packages'] ||= Settingslogic.new({})
+ Settings.packages['enabled'] = true if Settings.packages['enabled'].nil?
+ Settings.packages['storage_path'] = Settings.absolute(Settings.packages['storage_path'] || File.join(Settings.shared['path'], "packages"))
+ Settings.packages['object_store'] = ObjectStoreSettings.parse(Settings.packages['object_store'])
+end
+
+#
+# Dependency Proxy
+#
+Gitlab.ee do
+ Settings['dependency_proxy'] ||= Settingslogic.new({})
+ Settings.dependency_proxy['enabled'] = true if Settings.dependency_proxy['enabled'].nil?
+ Settings.dependency_proxy['storage_path'] = Settings.absolute(Settings.dependency_proxy['storage_path'] || File.join(Settings.shared['path'], "dependency_proxy"))
+ Settings.dependency_proxy['object_store'] = ObjectStoreSettings.parse(Settings.dependency_proxy['object_store'])
+
+ # For first iteration dependency proxy uses Rails server to download blobs.
+ # To ensure acceptable performance we only allow feature to be used with
+ # multithreaded web-server Puma. This will be removed once download logic is moved
+ # to GitLab workhorse
+ Settings.dependency_proxy['enabled'] = false unless defined?(::Puma)
+end
+
+#
# Mattermost
#
Settings['mattermost'] ||= Settingslogic.new({})
@@ -341,7 +408,6 @@ Settings.cron_jobs['remove_expired_group_links_worker']['job_class'] = 'RemoveEx
Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '0 */6 * * *'
Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker'
-
Settings.cron_jobs['trending_projects_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['trending_projects_worker']['cron'] = '0 1 * * *'
Settings.cron_jobs['trending_projects_worker']['job_class'] = 'TrendingProjectsWorker'
@@ -354,35 +420,70 @@ Settings.cron_jobs['stuck_import_jobs_worker']['job_class'] = 'StuckImportJobsWo
Settings.cron_jobs['gitlab_usage_ping_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['gitlab_usage_ping_worker']['cron'] ||= Settings.__send__(:cron_for_usage_ping)
Settings.cron_jobs['gitlab_usage_ping_worker']['job_class'] = 'GitlabUsagePingWorker'
-
Settings.cron_jobs['stuck_merge_jobs_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['stuck_merge_jobs_worker']['cron'] ||= '0 */2 * * *'
Settings.cron_jobs['stuck_merge_jobs_worker']['job_class'] = 'StuckMergeJobsWorker'
-
Settings.cron_jobs['pages_domain_verification_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['pages_domain_verification_cron_worker']['cron'] ||= '*/15 * * * *'
Settings.cron_jobs['pages_domain_verification_cron_worker']['job_class'] = 'PagesDomainVerificationCronWorker'
-
Settings.cron_jobs['pages_domain_removal_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['pages_domain_removal_cron_worker']['cron'] ||= '47 0 * * *'
Settings.cron_jobs['pages_domain_removal_cron_worker']['job_class'] = 'PagesDomainRemovalCronWorker'
-
Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker']['cron'] ||= '*/10 * * * *'
Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker']['job_class'] = 'PagesDomainSslRenewalCronWorker'
-
Settings.cron_jobs['issue_due_scheduler_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['issue_due_scheduler_worker']['cron'] ||= '50 00 * * *'
Settings.cron_jobs['issue_due_scheduler_worker']['job_class'] = 'IssueDueSchedulerWorker'
-
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'
+Gitlab.ee do
+ Settings.cron_jobs['clear_shared_runners_minutes_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['clear_shared_runners_minutes_worker']['cron'] ||= '0 0 1 * *'
+ Settings.cron_jobs['clear_shared_runners_minutes_worker']['job_class'] = 'ClearSharedRunnersMinutesWorker'
+ Settings.cron_jobs['geo_file_download_dispatch_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_file_download_dispatch_worker']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['geo_file_download_dispatch_worker']['job_class'] ||= 'Geo::FileDownloadDispatchWorker'
+ Settings.cron_jobs['geo_metrics_update_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_metrics_update_worker']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['geo_metrics_update_worker']['job_class'] ||= 'Geo::MetricsUpdateWorker'
+ Settings.cron_jobs['geo_migrated_local_files_clean_up_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_migrated_local_files_clean_up_worker']['cron'] ||= '15 */6 * * *'
+ Settings.cron_jobs['geo_migrated_local_files_clean_up_worker']['job_class'] ||= 'Geo::MigratedLocalFilesCleanUpWorker'
+ Settings.cron_jobs['geo_prune_event_log_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_prune_event_log_worker']['cron'] ||= '*/5 * * * *'
+ Settings.cron_jobs['geo_prune_event_log_worker']['job_class'] ||= 'Geo::PruneEventLogWorker'
+ Settings.cron_jobs['geo_repository_sync_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_repository_sync_worker']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['geo_repository_sync_worker']['job_class'] ||= 'Geo::RepositorySyncWorker'
+ Settings.cron_jobs['geo_repository_verification_primary_batch_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_repository_verification_primary_batch_worker']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['geo_repository_verification_primary_batch_worker']['job_class'] ||= 'Geo::RepositoryVerification::Primary::BatchWorker'
+ Settings.cron_jobs['geo_repository_verification_secondary_scheduler_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_repository_verification_secondary_scheduler_worker']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['geo_repository_verification_secondary_scheduler_worker']['job_class'] ||= 'Geo::RepositoryVerification::Secondary::SchedulerWorker'
+ Settings.cron_jobs['historical_data_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['historical_data_worker']['cron'] ||= '0 12 * * *'
+ Settings.cron_jobs['historical_data_worker']['job_class'] = 'HistoricalDataWorker'
+ Settings.cron_jobs['ldap_group_sync_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['ldap_group_sync_worker']['cron'] ||= '0 * * * *'
+ Settings.cron_jobs['ldap_group_sync_worker']['job_class'] = 'LdapAllGroupsSyncWorker'
+ Settings.cron_jobs['ldap_sync_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['ldap_sync_worker']['cron'] ||= '30 1 * * *'
+ Settings.cron_jobs['ldap_sync_worker']['job_class'] = 'LdapSyncWorker'
+ Settings.cron_jobs['pseudonymizer_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['pseudonymizer_worker']['cron'] ||= '0 23 * * *'
+ Settings.cron_jobs['pseudonymizer_worker']['job_class'] ||= 'PseudonymizerWorker'
+ Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker']['cron'] ||= '0 12 * * *'
+ Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker']['job_class'] = 'UpdateMaxSeatsUsedForGitlabComSubscriptionsWorker'
+end
+
#
# Sidekiq
#
@@ -462,6 +563,16 @@ Settings.backup['upload']['encryption_key'] ||= ENV['GITLAB_BACKUP_ENCRYPTION_KE
Settings.backup['upload']['storage_class'] ||= nil
#
+# Pseudonymizer
+#
+Gitlab.ee do
+ Settings['pseudonymizer'] ||= Settingslogic.new({})
+ Settings.pseudonymizer['manifest'] = Settings.absolute(Settings.pseudonymizer['manifest'] || Rails.root.join("config/pseudonymizer.yml"))
+ Settings.pseudonymizer['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
+ # Settings.pseudonymizer['upload']['multipart_chunk_size'] ||= 104857600
+end
+
+#
# Git
#
Settings['git'] ||= Settingslogic.new({})
@@ -474,6 +585,23 @@ Settings['satellites'] ||= Settingslogic.new({})
Settings.satellites['path'] = Settings.absolute(Settings.satellites['path'] || "tmp/repo_satellites/")
#
+# Kerberos
+#
+Gitlab.ee do
+ Settings['kerberos'] ||= Settingslogic.new({})
+ Settings.kerberos['enabled'] = false if Settings.kerberos['enabled'].nil?
+ Settings.kerberos['keytab'] = nil if Settings.kerberos['keytab'].blank? # nil means use default keytab
+ Settings.kerberos['service_principal_name'] = nil if Settings.kerberos['service_principal_name'].blank? # nil means any SPN in keytab
+ Settings.kerberos['use_dedicated_port'] = false if Settings.kerberos['use_dedicated_port'].nil?
+ Settings.kerberos['https'] = Settings.gitlab.https if Settings.kerberos['https'].nil?
+ Settings.kerberos['port'] ||= Settings.kerberos.https ? 8443 : 8088
+
+ if Settings.kerberos['enabled'] && !Settings.omniauth.providers.map(&:name).include?('kerberos_spnego')
+ Settings.omniauth.providers << Settingslogic.new({ 'name' => 'kerberos_spnego' })
+ end
+end
+
+#
# Extra customization
#
Settings['extra'] ||= Settingslogic.new({})
diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb
index bf9e5a50382..827b15e5c8d 100644
--- a/config/initializers/6_validations.rb
+++ b/config/initializers/6_validations.rb
@@ -1,24 +1,15 @@
-def storage_name_valid?(name)
- !!(name =~ /\A[a-zA-Z0-9\-_]+\z/)
-end
-
def storage_validation_error(message)
raise "#{message}. Please fix this in your gitlab.yml before starting GitLab."
end
def validate_storages_config
- storage_validation_error('No repository storage path defined') if Gitlab.config.repositories.storages.empty?
-
- Gitlab.config.repositories.storages.each do |name, repository_storage|
- storage_validation_error("\"#{name}\" is not a valid storage name") unless storage_name_valid?(name)
-
- %w(failure_count_threshold failure_reset_time storage_timeout).each do |setting|
- # Falling back to the defaults is fine!
- next if repository_storage[setting].nil?
+ if Gitlab.config.repositories.storages.empty?
+ storage_validation_error('No repository storage path defined')
+ end
- unless repository_storage[setting].to_f > 0
- storage_validation_error("`#{setting}` for storage `#{name}` needs to be greater than 0")
- end
+ Gitlab.config.repositories.storages.keys.each do |name|
+ unless /\A[a-zA-Z0-9\-_]+\z/.match?(name)
+ storage_validation_error("\"#{name}\" is not a valid storage name")
end
end
end
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 68f8487d377..54cdefc2a10 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -43,14 +43,21 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
end
end
-Gitlab::Cluster::LifecycleEvents.on_master_restart do
+def cleanup_prometheus_multiproc_dir
# The following is necessary to ensure stale Prometheus metrics don't
# accumulate over time. It needs to be done in this hook as opposed to
# inside an init script to ensure metrics files aren't deleted after new
# unicorn workers start after a SIGUSR2 is received.
- prometheus_multiproc_dir = ENV['prometheus_multiproc_dir']
- if prometheus_multiproc_dir
- old_metrics = Dir[File.join(prometheus_multiproc_dir, '*.db')]
+ if dir = ::Prometheus::Client.configuration.multiprocess_files_dir
+ old_metrics = Dir[File.join(dir, '*.db')]
FileUtils.rm_rf(old_metrics)
end
end
+
+Gitlab::Cluster::LifecycleEvents.on_master_start do
+ cleanup_prometheus_multiproc_dir
+end
+
+Gitlab::Cluster::LifecycleEvents.on_master_restart do
+ cleanup_prometheus_multiproc_dir
+end
diff --git a/config/initializers/jira.rb b/config/initializers/jira.rb
index 05f784a6a2a..664f9c87808 100644
--- a/config/initializers/jira.rb
+++ b/config/initializers/jira.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-# Changes JIRA DVCS user agent requests in order to be successfully handled
+# Changes Jira DVCS user agent requests in order to be successfully handled
# by our API.
#
# Gitlab::Jira::Middleware is only defined on EE
diff --git a/config/initializers/transaction_metrics.rb b/config/initializers/transaction_metrics.rb
new file mode 100644
index 00000000000..0175d487e66
--- /dev/null
+++ b/config/initializers/transaction_metrics.rb
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+Gitlab::Database.install_monkey_patches
diff --git a/config/routes/project.rb b/config/routes/project.rb
index bcbbd7222e0..91613e3333f 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -261,6 +261,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :commits
get :pipelines
get :diffs, to: 'merge_requests/diffs#show'
+ get :widget, to: 'merge_requests/content#widget'
end
get :diff_for_path, controller: 'merge_requests/diffs'
diff --git a/config/routes/repository.rb b/config/routes/repository.rb
index 1ea0ae72614..b89e1c7f9af 100644
--- a/config/routes/repository.rb
+++ b/config/routes/repository.rb
@@ -52,7 +52,10 @@ scope format: false do
end
get '/branches/:state', to: 'branches#index', as: :branches_filtered, constraints: { state: /active|stale|all/ }
- resources :branches, only: [:index, :new, :create, :destroy]
+ resources :branches, only: [:index, :new, :create, :destroy] do
+ get :diverging_commit_counts, on: :collection
+ end
+
delete :merged_branches, controller: 'branches', action: :destroy_all_merged
resources :tags, only: [:index, :show, :new, :create, :destroy] do
resource :release, controller: 'tags/releases', only: [:edit, :update]
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index 4637eb8bc6e..581fde84c95 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -88,9 +88,21 @@ before_exec do |server|
Gitlab::Cluster::LifecycleEvents.do_master_restart
end
+run_once = true
+
before_fork do |server, worker|
- # Signal application hooks that we're about to fork
- Gitlab::Cluster::LifecycleEvents.do_before_fork
+ if run_once
+ # There is a difference between Puma and Unicorn:
+ # - Puma calls before_fork once when booting up master process
+ # - Unicorn runs before_fork whenever new work is spawned
+ # To unify this behavior we call before_fork only once (we use
+ # this callback for deleting Prometheus files so for our purposes
+ # it makes sense to align behavior with Puma)
+ run_once = false
+
+ # Signal application hooks that we're about to fork
+ Gitlab::Cluster::LifecycleEvents.do_before_fork
+ end
# The following is only recommended for memory/DB-constrained
# installations. It is not needed if your system can house
diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development
index ae3dc2e37e1..9a02d5f1007 100644
--- a/config/unicorn.rb.example.development
+++ b/config/unicorn.rb.example.development
@@ -21,9 +21,21 @@ before_exec do |server|
Gitlab::Cluster::LifecycleEvents.do_master_restart
end
+run_once = true
+
before_fork do |server, worker|
- # Signal application hooks that we're about to fork
- Gitlab::Cluster::LifecycleEvents.do_before_fork
+ if run_once
+ # There is a difference between Puma and Unicorn:
+ # - Puma calls before_fork once when booting up master process
+ # - Unicorn runs before_fork whenever new work is spawned
+ # To unify this behavior we call before_fork only once (we use
+ # this callback for deleting Prometheus files so for our purposes
+ # it makes sense to align behavior with Puma)
+ run_once = false
+
+ # Signal application hooks that we're about to fork
+ Gitlab::Cluster::LifecycleEvents.do_before_fork
+ end
# The following is only recommended for memory/DB-constrained
# installations. It is not needed if your system can house
diff --git a/db/fixtures/development/24_forks.rb b/db/fixtures/development/24_forks.rb
index 5eb5956ec74..d05d27c3ed5 100644
--- a/db/fixtures/development/24_forks.rb
+++ b/db/fixtures/development/24_forks.rb
@@ -13,9 +13,9 @@ Sidekiq::Testing.inline! do
fork_project = Projects::ForkService.new(source_project, user, namespace: user.namespace).execute
if fork_project.valid?
- puts '.'
+ print '.'
else
- puts 'F'
+ print 'F'
end
end
end
diff --git a/db/migrate/20190613073003_create_project_aliases.rb b/db/migrate/20190613073003_create_project_aliases.rb
new file mode 100644
index 00000000000..5a2c2ba0cf2
--- /dev/null
+++ b/db/migrate/20190613073003_create_project_aliases.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateProjectAliases < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :project_aliases do |t|
+ t.references :project, null: false, index: true, foreign_key: { on_delete: :cascade }, type: :integer
+ t.string :name, null: false, index: { unique: true }
+
+ t.timestamps_with_timezone null: false
+ end
+ end
+end
diff --git a/db/migrate/20190623212503_add_cluster_id_to_deployments.rb b/db/migrate/20190623212503_add_cluster_id_to_deployments.rb
new file mode 100644
index 00000000000..cd0c4191568
--- /dev/null
+++ b/db/migrate/20190623212503_add_cluster_id_to_deployments.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddClusterIdToDeployments < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def change
+ add_column :deployments, :cluster_id, :integer
+ end
+end
diff --git a/db/migrate/20190627051902_add_cluster_id_index_fk_to_deployments.rb b/db/migrate/20190627051902_add_cluster_id_index_fk_to_deployments.rb
new file mode 100644
index 00000000000..f41e5c80269
--- /dev/null
+++ b/db/migrate/20190627051902_add_cluster_id_index_fk_to_deployments.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class AddClusterIdIndexFkToDeployments < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :deployments, :cluster_id
+
+ add_concurrent_foreign_key :deployments, :clusters, column: :cluster_id, on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key :deployments, :clusters
+
+ remove_concurrent_index :deployments, :cluster_id
+ end
+end
diff --git a/db/migrate/20190628145246_add_strategies_to_operations_feature_flag_scopes.rb b/db/migrate/20190628145246_add_strategies_to_operations_feature_flag_scopes.rb
new file mode 100644
index 00000000000..ed1f16ee69a
--- /dev/null
+++ b/db/migrate/20190628145246_add_strategies_to_operations_feature_flag_scopes.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddStrategiesToOperationsFeatureFlagScopes < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :operations_feature_flag_scopes, :strategies, :jsonb, default: [{ name: "default", parameters: {} }]
+ end
+
+ def down
+ remove_column(:operations_feature_flag_scopes, :strategies)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 054dbc7201f..8876be1cb34 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: 20190625184066) do
+ActiveRecord::Schema.define(version: 20190628145246) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1066,6 +1066,8 @@ ActiveRecord::Schema.define(version: 20190625184066) do
t.string "on_stop"
t.integer "status", limit: 2, null: false
t.datetime_with_timezone "finished_at"
+ t.integer "cluster_id"
+ t.index ["cluster_id"], name: "index_deployments_on_cluster_id", using: :btree
t.index ["created_at"], name: "index_deployments_on_created_at", using: :btree
t.index ["deployable_type", "deployable_id"], name: "index_deployments_on_deployable_type_and_deployable_id", using: :btree
t.index ["environment_id", "id"], name: "index_deployments_on_environment_id_and_id", using: :btree
@@ -2261,6 +2263,7 @@ ActiveRecord::Schema.define(version: 20190625184066) do
t.datetime_with_timezone "updated_at", null: false
t.boolean "active", null: false
t.string "environment_scope", default: "*", null: false
+ t.jsonb "strategies", default: [{"name"=>"default", "parameters"=>{}}], null: false
t.index ["feature_flag_id", "environment_scope"], name: "index_feature_flag_scopes_on_flag_id_and_environment_scope", unique: true, using: :btree
end
@@ -2412,6 +2415,15 @@ ActiveRecord::Schema.define(version: 20190625184066) do
t.string "encrypted_token_iv", null: false
end
+ create_table "project_aliases", force: :cascade do |t|
+ t.integer "project_id", null: false
+ t.string "name", null: false
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.index ["name"], name: "index_project_aliases_on_name", unique: true, using: :btree
+ t.index ["project_id"], name: "index_project_aliases_on_project_id", using: :btree
+ end
+
create_table "project_authorizations", id: false, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "project_id", null: false
@@ -3650,6 +3662,7 @@ ActiveRecord::Schema.define(version: 20190625184066) do
add_foreign_key "dependency_proxy_blobs", "namespaces", column: "group_id", name: "fk_db58bbc5d7", on_delete: :cascade
add_foreign_key "dependency_proxy_group_settings", "namespaces", column: "group_id", name: "fk_616ddd680a", on_delete: :cascade
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
+ add_foreign_key "deployments", "clusters", name: "fk_289bba3222", on_delete: :nullify
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
add_foreign_key "design_management_designs", "issues", on_delete: :cascade
add_foreign_key "design_management_designs", "projects", on_delete: :cascade
@@ -3793,6 +3806,7 @@ ActiveRecord::Schema.define(version: 20190625184066) do
add_foreign_key "pool_repositories", "projects", column: "source_project_id", on_delete: :nullify
add_foreign_key "pool_repositories", "shards", on_delete: :restrict
add_foreign_key "project_alerting_settings", "projects", on_delete: :cascade
+ add_foreign_key "project_aliases", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "projects", on_delete: :cascade
add_foreign_key "project_authorizations", "users", on_delete: :cascade
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index cfda2c9293d..489c8117b9d 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -209,7 +209,7 @@ The following documentation relates to the DevOps **Create** stage:
| [GitLab API](api/README.md) | Integrate GitLab via a simple and powerful API. |
| [GitLab Integration](integration/README.md) | Integrate with multiple third-party services with GitLab to allow external issue trackers and external authentication. |
| [GitLab Webhooks](user/project/integrations/webhooks.md) | Let GitLab notify you when new code has been pushed to your project. |
-| [JIRA Development Panel](integration/jira_development_panel.md) **[PREMIUM]** | See GitLab information in the JIRA Development Panel. |
+| [Jira Development Panel](integration/jira_development_panel.md) **[PREMIUM]** | See GitLab information in the Jira Development Panel. |
| [Project Services](user/project/integrations/project_services.md) | Integrate a project with external services, such as CI and chat. |
| [Trello Power-Up](integration/trello_power_up.md) | Integrate with GitLab's Trello Power-Up. |
diff --git a/doc/administration/database_load_balancing.md b/doc/administration/database_load_balancing.md
index 7f3be402b84..98404ff2a10 100644
--- a/doc/administration/database_load_balancing.md
+++ b/doc/administration/database_load_balancing.md
@@ -40,16 +40,16 @@ For example, say you have a primary (`db1.gitlab.com`) and two secondaries,
`db2.gitlab.com` and `db3.gitlab.com`. For this setup you will need to have 3
load balancers, one for every host. For example:
-* `primary.gitlab.com` forwards to `db1.gitlab.com`
-* `secondary1.gitlab.com` forwards to `db2.gitlab.com`
-* `secondary2.gitlab.com` forwards to `db3.gitlab.com`
+- `primary.gitlab.com` forwards to `db1.gitlab.com`
+- `secondary1.gitlab.com` forwards to `db2.gitlab.com`
+- `secondary2.gitlab.com` forwards to `db3.gitlab.com`
Now let's say that a failover happens and db2 becomes the new primary. This
means forwarding should now happen as follows:
-* `primary.gitlab.com` forwards to `db2.gitlab.com`
-* `secondary1.gitlab.com` forwards to `db1.gitlab.com`
-* `secondary2.gitlab.com` forwards to `db3.gitlab.com`
+- `primary.gitlab.com` forwards to `db2.gitlab.com`
+- `secondary1.gitlab.com` forwards to `db1.gitlab.com`
+- `secondary2.gitlab.com` forwards to `db3.gitlab.com`
GitLab does not take care of this for you, so you will need to do so yourself.
@@ -209,9 +209,9 @@ without it immediately leading to errors being presented to the users.
The load balancer logs various messages, such as:
-* When a host is marked as offline
-* When a host comes back online
-* When all secondaries are offline
+- When a host is marked as offline
+- When a host comes back online
+- When all secondaries are offline
Each log message contains the tag `[DB-LB]` to make searching/filtering of such
log entries easier. For example:
diff --git a/doc/administration/geo/replication/high_availability.md b/doc/administration/geo/replication/high_availability.md
index 921a3ef1c7a..28ad89c4446 100644
--- a/doc/administration/geo/replication/high_availability.md
+++ b/doc/administration/geo/replication/high_availability.md
@@ -79,9 +79,9 @@ The **primary** database will require modification later, as part of
A **secondary** cluster is similar to any other GitLab HA cluster, with two
major differences:
-* The main PostgreSQL database is a read-only replica of the **primary** node's
+- The main PostgreSQL database is a read-only replica of the **primary** node's
PostgreSQL database.
-* There is also a single PostgreSQL database for the **secondary** cluster,
+- There is also a single PostgreSQL database for the **secondary** cluster,
called the "tracking database", which tracks the synchronization state of
various resources.
@@ -93,9 +93,9 @@ from the normal HA setup.
Configure the following services, again using the non-Geo high availability
documentation:
-* [Configuring Redis for GitLab HA](../../high_availability/redis.md) for high
+- [Configuring Redis for GitLab HA](../../high_availability/redis.md) for high
availability.
-* [NFS](../../high_availability/nfs.md) which will store data that is
+- [NFS](../../high_availability/nfs.md) which will store data that is
synchronized from the **primary** node.
### Step 2: Configure the main read-only replica PostgreSQL database on the **secondary** node
@@ -270,15 +270,15 @@ After making these changes [Reconfigure GitLab][gitlab-reconfigure] so the chang
On the secondary the following GitLab frontend services will be enabled:
-* geo-logcursor
-* gitlab-pages
-* gitlab-workhorse
-* logrotate
-* nginx
-* registry
-* remote-syslog
-* sidekiq
-* unicorn
+- geo-logcursor
+- gitlab-pages
+- gitlab-workhorse
+- logrotate
+- nginx
+- registry
+- remote-syslog
+- sidekiq
+- unicorn
Verify these services by running `sudo gitlab-ctl status` on the frontend
application servers.
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index 5394e6dd763..5bd6cc81362 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -504,6 +504,15 @@ To resolve this, run the following command:
sudo gitlab-rake geo:db:refresh_foreign_tables
```
+## Expired artifacts
+
+If you notice for some reason there are more artifacts on the Geo
+secondary node than on the Geo primary node, you can use the rake task
+to [cleanup orphan artifact files](../../../raketasks/cleanup.md#remove-orphan-artifact-files).
+
+On a Geo **secondary** node, this command will also clean up all Geo
+registry record related to the orphan files on disk.
+
## Fixing common errors
This section documents common errors reported in the Admin UI and how to fix them.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index da8f1ee1529..0bffe2f7472 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -48,7 +48,7 @@ used by Omnibus and the GitLab source installation guide.
Starting with GitLab 11.4, Gitaly is able to serve all Git requests without
needed a shared NFS mount for Git repository data.
Between 11.4 and 11.8 the exception was the
-[Elastic Search indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
+[Elasticsearch indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
But since 11.8 the indexer uses Gitaly for data access as well. NFS can still
be leveraged for redudancy on block level of the Git data. But only has to
be mounted on the Gitaly server.
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 022c23d02ce..9921ffd8ea0 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -125,7 +125,7 @@ This file lives in `/var/log/gitlab/gitlab-rails/integrations_json.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/integrations_json.log` for
installations from source.
-It contains information about [integrations](../user/project/integrations/project_services.md) activities such as JIRA, Asana and Irker services. It uses JSON format like the example below:
+It contains information about [integrations](../user/project/integrations/project_services.md) activities such as Jira, Asana and Irker services. It uses JSON format like the example below:
``` json
{"severity":"ERROR","time":"2018-09-06T14:56:20.439Z","service_class":"JiraService","project_id":8,"project_path":"h5bp/html5-boilerplate","message":"Error sending message","client_url":"http://jira.gitlap.com:8080","error":"execution expired"}
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 7b58aa3100e..85a07589956 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -1473,7 +1473,7 @@ Example response when the GitLab issue tracker is used:
]
```
-Example response when an external issue tracker (e.g. JIRA) is used:
+Example response when an external issue tracker (e.g. Jira) is used:
```json
[
diff --git a/doc/api/project_aliases.md b/doc/api/project_aliases.md
new file mode 100644
index 00000000000..65845579409
--- /dev/null
+++ b/doc/api/project_aliases.md
@@ -0,0 +1,105 @@
+# Project Aliases API **[PREMIUM ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3264) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.1.
+
+All methods require administrator authorization.
+
+## List all project aliases
+
+Get a list of all project aliases:
+
+```
+GET /project_aliases
+```
+
+```
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_aliases"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "project_id": 1,
+ "name": "gitlab-ce"
+ },
+ {
+ "id": 2,
+ "project_id": 2,
+ "name": "gitlab-ee"
+ }
+]
+```
+
+## Get project alias' details
+
+Get details of a project alias:
+
+```
+GET /project_aliases/:name
+```
+
+| Attribute | Type | Required | Description |
+|-----------|--------|----------|-----------------------|
+| `name` | string | yes | The name of the alias |
+
+```
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_aliases/gitlab-ee"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "project_id": 1,
+ "name": "gitlab-ee"
+}
+```
+
+## Create a project alias
+
+Add a new alias for a project. Responds with a 201 when successful,
+400 when there are validation errors (e.g. alias already exists):
+
+```
+POST /project_aliases
+```
+
+| Attribute | Type | Required | Description |
+|--------------|--------|----------|-----------------------------------------------|
+| `project_id` | string | yes | The ID or URL-encoded path of the project. |
+| `name` | string | yes | The name of the alias. Must be unique. |
+
+```
+curl --request POST "https://gitlab.example.com/api/v4/project_aliases" --form "project_id=gitlab-org%2Fgitlab-ee" --form "name=gitlab-ee"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "project_id": 1,
+ "name": "gitlab-ee"
+}
+```
+
+## Delete a project alias
+
+Removes a project aliases. Responds with a 204 when project alias
+exists, 404 when it doesn't:
+
+```
+DELETE /project_aliases/:name
+```
+
+| Attribute | Type | Required | Description |
+|-----------|--------|----------|-----------------------|
+| `name` | string | yes | The name of the alias |
+
+```
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/project_aliases/gitlab-ee"
+```
diff --git a/doc/api/services.md b/doc/api/services.md
index 042fee4a21a..2368f36e444 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -551,21 +551,21 @@ Get Irker (IRC gateway) service settings for a project.
GET /projects/:id/services/irker
```
-## JIRA
+## Jira
-JIRA issue tracker.
+Jira issue tracker.
-### Get JIRA service settings
+### Get Jira service settings
-Get JIRA service settings for a project.
+Get Jira service settings for a project.
```
GET /projects/:id/services/jira
```
-### Create/Edit JIRA service
+### Create/Edit Jira service
-Set JIRA service for a project.
+Set Jira service for a project.
> Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and
> `project_url` are replaced by `url`. If you are using an
@@ -579,18 +579,18 @@ Parameters:
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `url` | string | yes | The URL to the JIRA project which is being linked to this GitLab project. For example, `https://jira.example.com`. |
-| `api_url` | string | no | The base URL to the JIRA instance API. Web URL value will be used if not set. For example, `https://jira-api.example.com`. |
-| `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. |
+| `url` | string | yes | The URL to the Jira project which is being linked to this GitLab project. For example, `https://jira.example.com`. |
+| `api_url` | string | no | The base URL to the Jira instance API. Web URL value will be used if not set. For example, `https://jira-api.example.com`. |
+| `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` | 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`. |
+| `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`. |
| `commit_events` | boolean | false | Enable notifications for commit events |
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
-### Delete JIRA service
+### Delete Jira service
-Remove all previously JIRA settings from a project.
+Remove all previously Jira settings from a project.
```
DELETE /projects/:id/services/jira
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index f752f942e24..e012f4f8595 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -463,8 +463,6 @@ that runner.
> support for using private registries, which required manual configuration
> of credentials on runner's host. We recommend to upgrade your Runner to
> at least version **1.8** if you want to use private registries.
-> - If the repository is private you need to authenticate your GitLab Runner in the
-> registry. Learn more about how [GitLab Runner works in this case][runner-priv-reg].
To access private container registries, the GitLab Runner process can use:
@@ -489,6 +487,19 @@ it's provided as an environment variable. This is because GitLab Runnner uses **
runtime.
### Using statically-defined credentials
+There are two approaches that you can take in order to access a
+private registry. Both require setting the environment variable
+`DOCKER_AUTH_LOGIN` with appropriate authentication info.
+
+1. Per-job: To configure one job to access a private registry, add
+ `DOCKER_AUTH_LOGIN` as a job variable.
+1. Per-runner: To configure a Runner so all its jobs can access a
+ private registry, add `DOCKER_AUTH_LOGIN` to the environment in the
+ Runner's configuration.
+
+See below for examples of each.
+
+#### Determining your `DOCKER_AUTH_LOGIN` data
As an example, let's assume that you want to use the `registry.example.com:5000/private/image:latest`
image which is private and requires you to login into a private container registry.
@@ -501,30 +512,41 @@ Let's also assume that these are the login credentials:
| username | `my_username` |
| password | `my_password` |
-To configure access for `registry.example.com:5000`, follow these steps:
+There are two ways to determine the value of `DOCKER_AUTH_CONFIG`:
+
+- **First way -** Do a `docker login` on your local machine:
-1. Find what the value of `DOCKER_AUTH_CONFIG` should be. There are two ways to
- accomplish this:
- - **First way -** Do a `docker login` on your local machine:
+ ```bash
+ docker login registry.example.com:5000 --username my_username --password my_password
+ ```
- ```bash
- docker login registry.example.com:5000 --username my_username --password my_password
- ```
+ Then copy the content of `~/.docker/config.json`.
- Then copy the content of `~/.docker/config.json`.
- - **Second way -** In some setups, it's possible that Docker client will use
- the available system keystore to store the result of `docker login`. In
- that case, it's impossible to read `~/.docker/config.json`, so you will
- need to prepare the required base64-encoded version of
- `${username}:${password}` manually. Open a terminal and execute the
- following command:
+ If you don't need access to the registry from your computer, you
+ can do a `docker logout`:
+
+ ```bash
+ docker logout registry.example.com:5000
+ ```
+
+- **Second way -** In some setups, it's possible that Docker client
+will use the available system keystore to store the result of `docker
+login`. In that case, it's impossible to read `~/.docker/config.json`,
+so you will need to prepare the required base64-encoded version of
+`${username}:${password}` manually. Open a terminal and execute the
+following command:
+
+ ```bash
+ echo -n "my_username:my_password" | base64
+
+ # Example output to copy
+ bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=
+ ```
- ```bash
- echo -n "my_username:my_password" | base64
+#### Configuring a job
- # Example output to copy
- bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=
- ```
+To configure a single job with access for `registry.example.com:5000`,
+follow these steps:
1. Create a [variable](../variables/README.md#gitlab-cicd-environment-variables) `DOCKER_AUTH_CONFIG` with the content of the
Docker configuration file as the value:
@@ -539,14 +561,6 @@ To configure access for `registry.example.com:5000`, follow these steps:
}
```
-1. Optionally,if you followed the first way of finding the `DOCKER_AUTH_CONFIG`
- value, do a `docker logout` on your computer if you don't need access to the
- registry from it:
-
- ```bash
- docker logout registry.example.com:5000
- ```
-
1. You can now use any private image from `registry.example.com:5000` defined in
`image` and/or `services` in your `.gitlab-ci.yml` file:
@@ -567,6 +581,38 @@ for the Runner to match the `DOCKER_AUTH_CONFIG`. For example, if
then the `DOCKER_AUTH_CONFIG` must also specify `registry.example.com:5000`.
Specifying only `registry.example.com` will not work.
+### Configuring a Runner
+
+If you have many pipelines that access the same registry, it'll
+probably be better to setup registry access at the runner level. This
+allows pipeline authors to have access to a private registry just by
+running a job on the appropriate runner. It also makes registry
+changes and credential rotations much simpler.
+
+Of course this means that any job on that runner can access the
+registry with the same privilege, even across projects. If you need to
+control access to the registry, you'll need to be sure to control
+access to the runner.
+
+To add `DOCKER_AUTH_CONFIG` to a Runner:
+
+1. Modify the Runner's `config.toml` file as follows:
+
+ ```toml
+ [[runners]]
+ environment = ["DOCKER_AUTH_CONFIG={\"auths\":{\"registry.example.com:5000\":{\"auth\":\"bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=\"}}}"]
+ ```
+
+1. Restart the Runner service.
+
+NOTE: **Note:** The double quotes included in the `DOCKER_AUTH_CONFIG`
+data must be escaped with backslashes. This prevents them from being
+interpreted as TOML.
+
+NOTE: **Note:** The `environment` option is a list. So your Runner may
+have existing entries and you should add this to the list, not replace
+it.
+
### Using Credentials Store
> Support for using Credentials Store was added in GitLab Runner 9.5.
@@ -741,7 +787,6 @@ creation.
[tutum/wordpress]: https://hub.docker.com/r/tutum/wordpress/
[postgres-hub]: https://hub.docker.com/r/_/postgres/
[mysql-hub]: https://hub.docker.com/r/_/mysql/
-[runner-priv-reg]: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry
[entrypoint]: https://docs.docker.com/engine/reference/builder/#entrypoint
[cmd]: https://docs.docker.com/engine/reference/builder/#cmd
[register]: https://docs.gitlab.com/runner/register/
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index 5adb7ebd30d..c3dbcf6a19f 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -4,12 +4,6 @@ type: reference
# Pipelines for merge requests
-NOTE: **Note**:
-As of GitLab 11.10, pipelines for merge requests require GitLab Runner 11.9
-or higher due to the [recent refspecs
-changes](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25504).
-Anything lower will cause the pipeline to fail.
-
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/15310) in GitLab 11.6.
Usually, when you create a new merge request, a pipeline runs with the
@@ -23,6 +17,16 @@ for when you are running a pipeline in a merge request. This
could be either adding or removing steps in the pipeline, to make sure that
your pipelines are as efficient as possible.
+## Requirements and limitations
+
+Pipelines for merge requests have the following requirements and limitations:
+
+- As of GitLab 11.10, pipelines for merge requests require GitLab Runner 11.9
+ or higher due to the
+ [recent refspecs changes](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25504).
+- Pipelines for merge requests are incompatible with
+ [CI/CD for external repositories](../ci_cd_for_external_repos/index.md).
+
## Configuring pipelines for merge requests
To configure pipelines for merge requests, add the `only: merge_requests` parameter to
@@ -71,7 +75,7 @@ when a merge request was created or updated. For example:
![Merge request page](img/merge_request.png)
-## Pipelines for Merged Results **[PREMIUM]**
+## Pipelines for merged results **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7380) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.10.
> This feature is disabled by default until we resolve issues with [contention handling](https://gitlab.com/gitlab-org/gitlab-ee/issues/11222), but [can be enabled manually](#enabling-pipelines-for-merged-results).
@@ -100,7 +104,22 @@ The detached state serves to warn you that you are working in a situation
subjected to merge problems, and helps to highlight that you should
get out of WIP status or resolve merge conflicts as soon as possible.
-### Enabling Pipelines for Merged Results
+### Requirements and limitations
+
+Pipelines for merged results require:
+
+- [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner) 11.9 or newer.
+- [Gitaly](https://gitlab.com/gitlab-org/gitaly) 1.21.0 or newer.
+
+In addition, pipelines for merged results have the following limitations:
+
+- Forking/cross-repo workflows are not currently supported. To follow progress,
+ see [#9713](https://gitlab.com/gitlab-org/gitlab-ee/issues/9713).
+- This feature is not available for
+ [fast forward merges](../../user/project/merge_requests/fast_forward_merge.md) yet.
+ To follow progress, see [#58226](https://gitlab.com/gitlab-org/gitlab-ce/issues/58226).
+
+### Enabling Pipelines for merged results
To enable pipelines on merged results at the project level:
@@ -114,13 +133,6 @@ CAUTION: **Warning:**
Make sure your `gitlab-ci.yml` file is [configured properly for pipelines for merge requests](#configuring-pipelines-for-merge-requests),
otherwise pipelines for merged results won't run and your merge requests will be stuck in an unresolved state.
-### Pipelines for Merged Result's limitations
-
-- This feature requires [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner) 11.9 or newer.
-- This feature requires [Gitaly](https://gitlab.com/gitlab-org/gitaly) 1.21.0 or newer.
-- Forking/cross-repo workflows are not currently supported. To follow progress, see [#9713](https://gitlab.com/gitlab-org/gitlab-ee/issues/9713).
-- This feature is not available for [fast forward merges](../../user/project/merge_requests/fast_forward_merge.md) yet. To follow progress, see [#58226](https://gitlab.com/gitlab-org/gitlab-ce/issues/58226).
-
## Merge Trains **[PREMIUM]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9186) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.0.
@@ -128,6 +140,7 @@ otherwise pipelines for merged results won't run and your merge requests will be
[Pipelines for merged results](#pipelines-for-merged-results-premium) introduces
running a build on the result of the merged code prior to merging, as a way to keep master green.
+
There's a scenario, however, for teams with a high number of changes in the target branch (typically master) where in many or even all cases,
by the time the merged code is validated another commit has made it to master, invalidating the merged result.
You'd need some kind of queuing, cancellation or retry mechanism for these scenarios
@@ -137,13 +150,23 @@ Each MR that joins a merge train joins as the last item in the train,
just as it works in the current state. However, instead of queuing and waiting,
each item takes the completed state of the previous (pending) merge ref, adds its own changes,
and starts the pipeline immediately in parallel under the assumption that everything is going to pass.
+
In this way, if all the pipelines in the train merge successfully, no pipeline time is wasted either queuing or retrying.
If the button is subsequently pressed in a different MR, instead of creating a new pipeline for the target branch,
it creates a new pipeline targeting the merge result of the previous MR plus the target branch.
Pipelines invalidated through failures are immediately canceled and requeued.
-CAUTION: **Caution:**
-At the moment, each merge train can generate a merge ref and run a pipeline **one at a time**. We plan to make the pipelines for merged results [run in parallel](https://gitlab.com/gitlab-org/gitlab-ee/issues/11222) in a future release.
+### Requirements and limitations
+
+Merge trains have the following requirements and limitations:
+
+- This feature requires that
+ [pipelines for merged results](#pipelines-for-merged-results-premium) are
+ **configured properly**.
+- Each merge train can generate a merge ref and run a pipeline **one at a time**.
+ We plan to make the pipelines for merged results
+ [run in parallel](https://gitlab.com/gitlab-org/gitlab-ee/issues/11222) in a
+ future release.
### Enabling Merge Trains
@@ -155,9 +178,6 @@ To enable merge trains at the project level:
![Merge request pipeline config](img/merge_train_config.png)
-CAUTION: **Warning:**
-This feature requires [Pipelines for merged results](#pipelines-for-merged-results-premium) to be **configured properly**.
-
### How to add a merge request to a merge train
To add a merge request to a merge train:
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index d14a760f972..87735751c4a 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -93,8 +93,8 @@ graph TB
Prometheus --> Alertmanager
Migrations --> PostgreSQL
Runner -- TCP 443 --> NGINX
- Unicorn -- TCP 9200 --> ElasticSearch
- Sidekiq -- TCP 9200 --> ElasticSearch
+ Unicorn -- TCP 9200 --> Elasticsearch
+ Sidekiq -- TCP 9200 --> Elasticsearch
Sidekiq -- TCP 80, 443 --> Sentry
Unicorn -- TCP 80, 443 --> Sentry
Sidekiq -- UDP 6831 --> Jaeger
@@ -116,10 +116,10 @@ graph TB
### Component legend
-* ✅ - Installed by default
-* ⚙ - Requires additional configuration, or GitLab Managed Apps
-* ⤓ - Manual installation required
-* ❌ - Not supported or no instructions available
+- ✅ - Installed by default
+- ⚙ - Requires additional configuration, or GitLab Managed Apps
+- ⤓ - Manual installation required
+- ❌ - Not supported or no instructions available
Component statuses are linked to configuration documentation for each component.
@@ -158,7 +158,7 @@ Component statuses are linked to configuration documentation for each component.
| [LDAP Authentication](#ldap-authentication) | Authenticate users against centralized LDAP directory | [⤓][ldap-omnibus] | [⤓][ldap-charts] | [⤓][ldap-charts] | [❌](https://about.gitlab.com/pricing/#gitlab-com) | [⤓][gitlab-yml] | [⤓][ldap-gdk] | CE & EE |
| [Outbound email (SMTP)](#outbound-email) | Send email messages to users | [⤓][outbound-email-omnibus] | [⤓][outbound-email-charts] | [⤓][outbound-email-charts] | [✅](../user/gitlab_com/index.md#mail-configuration) | [⤓][gitlab-yml] | [⤓][gitlab-yml] | CE & EE |
| [Inbound email (SMTP)](#inbound-email) | Receive messages to update issues | [⤓][inbound-email-omnibus] | [⤓][inbound-email-charts] | [⤓][inbound-email-charts] | [✅](../user/gitlab_com/index.md#mail-configuration) | [⤓][gitlab-yml] | [⤓][gitlab-yml] | CE & EE |
-| [ElasticSearch](#elasticsearch) | Improved search within GitLab | [⤓][elasticsearch-omnibus] | [⤓][elasticsearch-charts] | [⤓][elasticsearch-charts] | [❌](https://gitlab.com/groups/gitlab-org/-/epics/153) | [⤓][elasticsearch-source] | [⤓][elasticsearch-gdk] | EE Only |
+| [Elasticsearch](#elasticsearch) | Improved search within GitLab | [⤓][elasticsearch-omnibus] | [⤓][elasticsearch-charts] | [⤓][elasticsearch-charts] | [❌](https://gitlab.com/groups/gitlab-org/-/epics/153) | [⤓][elasticsearch-source] | [⤓][elasticsearch-gdk] | EE Only |
| [Sentry integration](#sentry) | Error tracking for deployed apps | [⤓][sentry-integration] | [⤓][sentry-integration] | [⤓][sentry-integration] | [⤓][sentry-integration] | [⤓][sentry-integration] | [⤓][sentry-integration] | CE & EE |
| [Jaeger integration](#jaeger) | Distributed tracing for deployed apps | [⤓][jaeger-integration] | [⤓][jaeger-integration] | [⤓][jaeger-integration] | [⤓][jaeger-integration] | [⤓][jaeger-integration] | [⤓][jaeger-integration] | EE Only |
| [GitLab Managed Apps](#gitlab-managed-apps) | Deploy [Helm](https://docs.helm.sh/), [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/), [Cert-Manager](https://docs.cert-manager.io/en/latest/), [Prometheus](https://prometheus.io/docs/introduction/overview/), a [Runner](https://docs.gitlab.com/runner/), [JupyterHub](http://jupyter.org/), [Knative](https://cloud.google.com/knative) to a cluster | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | [⤓][managed-k8s-apps] | CE & EE |
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 2f1ad5fa910..d9595bd7bba 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -43,6 +43,10 @@ The descriptions on the [labels page][labels-page] explain what falls under each
Subject labels are labels that define what area or feature of GitLab this issue
hits. They are not always necessary, but very convenient.
+Subject labels are now used to infer and apply relevant group and devops stage
+labels. Please apply them whenever possible to facilitate accurate matching.
+Please refer to [this merge request][inferred-labels] for more information.
+
Examples of subject labels are ~wiki, ~ldap, ~api,
~issues, ~"merge requests", ~labels, and ~"Container Registry".
@@ -188,20 +192,20 @@ There can be multiple facets of the impact. The below is a guideline.
If a bug seems to fall between two severity labels, assign it to the higher-severity label.
-* Example(s) of ~S1
- * Data corruption/loss.
- * Security breach.
- * Unable to create an issue or merge request.
- * Unable to add a comment or discussion to the issue or merge request.
-* Example(s) of ~S2
- * Cannot submit changes through the web IDE but the commandline works.
- * A status widget on the merge request page is not working but information can be seen in the test pipeline page.
-* Example(s) of ~S3
- * Can create merge requests only from the Merge Requests list view, not from an Issue page.
- * Status is not updated in real time and needs a page refresh.
-* Example(s) of ~S4
- * Label colors are incorrect.
- * UI elements are not fully aligned.
+- Example(s) of ~S1
+ - Data corruption/loss.
+ - Security breach.
+ - Unable to create an issue or merge request.
+ - Unable to add a comment or discussion to the issue or merge request.
+- Example(s) of ~S2
+ - Cannot submit changes through the web IDE but the commandline works.
+ - A status widget on the merge request page is not working but information can be seen in the test pipeline page.
+- Example(s) of ~S3
+ - Can create merge requests only from the Merge Requests list view, not from an Issue page.
+ - Status is not updated in real time and needs a page refresh.
+- Example(s) of ~S4
+ - Label colors are incorrect.
+ - UI elements are not fully aligned.
## Label for community contributors
@@ -444,3 +448,4 @@ A recent example of this was the issue for
[labels-page]: https://gitlab.com/gitlab-org/gitlab-ce/labels
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
+[inferred-labels]: https://gitlab.com/gitlab-org/quality/triage-ops/merge_requests/155
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index f319d00d7fe..87e61a7476f 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -31,8 +31,8 @@ This is also the style used by linting tools such as
[Return to Contributing documentation](index.md)
-[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
-[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
+[rss-source]: https://github.com/rubocop-hq/ruby-style-guide/blob/master/README.adoc#source-code-layout
+[rss-naming]: https://github.com/rubocop-hq/ruby-style-guide/blob/master/README.adoc#naming-conventions
[doc-guidelines]: ../documentation/index.md "Documentation guidelines"
[js-styleguide]: ../fe_guide/style_guide_js.md "JavaScript styleguide"
[scss-styleguide]: ../fe_guide/style_guide_scss.md "SCSS styleguide"
diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md
index 68d33a9d8e0..de2c5b43411 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -85,3 +85,21 @@ eric 37709 0.0 0.0 2518640 7524 s006 S Wed11AM 0:00.79 s
$ kill 87304
$ kill 37709
```
+
+### db:migrate `database version is too old to be migrated` error
+
+Users receive this error when `db:migrate` detects that the current schema version
+is older than the `MIN_SCHEMA_VERSION` defined in the `Gitlab::Database` library
+module.
+
+Over time we cleanup/combine old migrations in the codebase, so it is not always
+possible to migrate GitLab from every previous version.
+
+In some cases you may want to bypass this check. For example, if you were on a version
+of GitLab schema later than the `MIN_SCHEMA_VERSION`, and then rolled back the
+to an older migration, from before. In this case, in order to migrate forward again,
+you should set the `SKIP_SCHEMA_VERSION_CHECK` environment variable.
+
+```sh
+bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true
+```
diff --git a/doc/development/documentation/site_architecture/index.md b/doc/development/documentation/site_architecture/index.md
index ee3a9caf9a0..6dd12b5efa7 100644
--- a/doc/development/documentation/site_architecture/index.md
+++ b/doc/development/documentation/site_architecture/index.md
@@ -11,8 +11,40 @@ and deploy it to <https://docs.gitlab.com>.
While the source of the documentation content is stored in GitLab's respective product
repositories, the source that is used to build the documentation site _from that content_
-is located at <https://gitlab.com/gitlab-com/gitlab-docs>. See the README there for
-detailed information.
+is located at <https://gitlab.com/gitlab-com/gitlab-docs>.
+
+The following diagram illustrates the relationship between the repositories
+from where content is sourced, the `gitlab-docs` project, and the published output.
+
+```mermaid
+ graph LR
+ A[gitlab-ce/doc]
+ B[gitlab-ee/doc]
+ C[gitlab-runner/docs]
+ D[omnibus-gitlab/doc]
+ E[charts/doc]
+ F[gitlab-docs]
+ A --> F
+ B --> F
+ C --> F
+ D --> F
+ E --> F
+ F -- Build pipeline --> G
+ G[docs.gitlab.com]
+ H[/ce/]
+ I[/ee/]
+ J[/runner/]
+ K[/omnibus/]
+ L[/charts/]
+ G --> H
+ G --> I
+ G --> J
+ G --> K
+ G --> L
+```
+
+See the [README there](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md)
+for detailed information.
## Assets
@@ -22,9 +54,9 @@ the GitLab Documentation website.
### Libraries
-- [Bootstrap 3.3 components](https://getbootstrap.com/docs/3.3/components/)
-- [Bootstrap 3.3 JS](https://getbootstrap.com/docs/3.3/javascript/)
-- [jQuery](https://jquery.com/) 3.2.1
+- [Bootstrap 4.3.1 components](https://getbootstrap.com/docs/4.3/components/)
+- [Bootstrap 4.3.1 JS](https://getbootstrap.com/docs/4.3/getting-started/javascript/)
+- [jQuery](https://jquery.com/) 3.3.1
- [Clipboard JS](https://clipboardjs.com/)
- [Font Awesome 4.7.0](https://fontawesome.com/v4.7.0/icons/)
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 7cd3d82ec4e..0d82f905bf3 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -76,8 +76,8 @@ and cross-link between any related content.
We employ a **docs-first methodology** to help ensure that the docs remain a complete and trusted resource, and to make communicating about the use of GitLab more efficient.
-* If the answer to a question exists in documentation, share the link to the docs instead of rephrasing the information.
-* When you encounter new information not available in GitLab’s documentation (for example, when working on a support case or testing a feature), your first step should be to create a merge request to add this information to the docs. You can then share the MR in order to communicate this information.
+- If the answer to a question exists in documentation, share the link to the docs instead of rephrasing the information.
+- When you encounter new information not available in GitLab’s documentation (for example, when working on a support case or testing a feature), your first step should be to create a merge request to add this information to the docs. You can then share the MR in order to communicate this information.
New information that would be useful toward the future usage or troubleshooting of GitLab should not be written directly in a forum or other messaging system, but added to a docs MR and then referenced, as described above. Note that among any other doc changes, you can always add a Troubleshooting section to a doc if none exists, or un-comment and use the placeholder Troubleshooting section included as part of our [doc template](structure.md#template-for-new-docs), if present.
@@ -577,7 +577,7 @@ nicely on different mobile devices.
## Alert boxes
-Whenever you want to call the attention to a particular sentence,
+Whenever you need to call special attention to particular sentences,
use the following markup for highlighting.
_Note that the alert boxes only work for one paragraph only. Multiple paragraphs,
@@ -585,6 +585,23 @@ lists, headers, etc will not render correctly. For multiple lines, use blockquot
### Note
+Notes catch the eye of most readers, and therefore should be used very sparingly.
+In most cases, content considered for a note should be included:
+
+- As just another sentence in the previous paragraph or the most-relevant paragraph.
+- As its own standalone paragraph.
+- As content under a new subheading that introduces the topic, making it more visible/findable.
+
+#### When to use
+
+Use a note when there is a reason that most or all readers who browse the
+section should see the content. That is, if missed, it’s likely to cause
+major trouble for a minority of users or significant trouble for a majority
+of users.
+
+Weigh the costs of distracting users to whom the content is not relevant against
+the cost of users missing the content if it were not expressed as a note.
+
```md
NOTE: **Note:**
This is something to note.
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 6b416cf588c..34d41cf4958 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -959,10 +959,10 @@ import mixin from 'ee_else_ce/path/mixin';
#### `template` tag
-* **EE Child components**
+- **EE Child components**
- Since we are using the async loading to check which component to load, we'd still use the component's name, check [this example](#child-component-only-used-in-ee).
-* **EE extra HTML**
+- **EE extra HTML**
- For the templates that have extra HTML in EE we should move it into a new component and use the `ee_else_ce` dynamic import
### Non Vue Files
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 94b3796f6e9..05e64b33eec 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -2,14 +2,14 @@
This area is to maintain a compendium of useful information when working with elasticsearch.
-Information on how to enable ElasticSearch and perform the initial indexing is kept in ../integration/elasticsearch.md#enabling-elasticsearch
+Information on how to enable Elasticsearch and perform the initial indexing is kept in ../integration/elasticsearch.md#enabling-elasticsearch
## Deep Dive
-In June 2019, Mario de la Ossa hosted a [Deep Dive] on GitLab's [ElasticSearch integration] to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube], and the slides on [Google Slides] and in [PDF]. Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction.
+In June 2019, Mario de la Ossa hosted a [Deep Dive] on GitLab's [Elasticsearch integration] to share his domain specific knowledge with anyone who may work in this part of the code base in the future. You can find the [recording on YouTube], and the slides on [Google Slides] and in [PDF]. Everything covered in this deep dive was accurate as of GitLab 12.0, and while specific details may have changed since then, it should still serve as a good introduction.
[Deep Dive]: https://gitlab.com/gitlab-org/create-stage/issues/1
-[ElasticSearch integration]: ../integration/elasticsearch.md
+[Elasticsearch integration]: ../integration/elasticsearch.md
[recording on YouTube]: https://www.youtube.com/watch?v=vrvl-tN2EaA
[Google Slides]: https://docs.google.com/presentation/d/1H-pCzI_LNrgrL5pJAIQgvLX8Ji0-jIKOg1QeJQzChug/edit
[PDF]: https://gitlab.com/gitlab-org/create-stage/uploads/c5aa32b6b07476fa8b597004899ec538/Elasticsearch_Deep_Dive.pdf
@@ -57,7 +57,7 @@ Additionally, if you need large repos or multiple forks for testing, please cons
## How does it work?
-The ElasticSearch integration depends on an external indexer. We ship a [ruby indexer](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/bin/elastic_repo_indexer) by default but are also working on an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). The user must trigger the initial indexing via a rake task, but after this is done GitLab itself will trigger reindexing when required via `after_` callbacks on create, update, and destroy that are inherited from [/ee/app/models/concerns/elastic/application_search.rb](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/concerns/elastic/application_search.rb).
+The Elasticsearch integration depends on an external indexer. We ship a [ruby indexer](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/bin/elastic_repo_indexer) by default but are also working on an [indexer written in Go](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer). The user must trigger the initial indexing via a rake task, but after this is done GitLab itself will trigger reindexing when required via `after_` callbacks on create, update, and destroy that are inherited from [/ee/app/models/concerns/elastic/application_search.rb](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/models/concerns/elastic/application_search.rb).
All indexing after the initial one is done via `ElasticIndexerWorker` (sidekiq jobs).
@@ -186,6 +186,6 @@ cluster.routing.allocation.disk.watermark.low: 15gb
cluster.routing.allocation.disk.watermark.high: 10gb
```
-Restart ElasticSearch, and the `read_only_allow_delete` will clear on it's own.
+Restart Elasticsearch, and the `read_only_allow_delete` will clear on it's own.
_from "Disk-based Shard Allocation | Elasticsearch Reference" [5.6](https://www.elastic.co/guide/en/elasticsearch/reference/5.6/disk-allocator.html#disk-allocator) and [6.x](https://www.elastic.co/guide/en/elasticsearch/reference/6.x/disk-allocator.html)_
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 8c6a73c6824..020eede8a03 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -123,7 +123,7 @@ Check this [page](vuex.md) for more details.
### Mixing Vue and jQuery
- Mixing Vue and jQuery is not recommended.
-- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it][https://vuejs.org/v2/examples/select2.html].
+- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it](https://vuejs.org/v2/examples/select2.html).
- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
- It is not recommended to add new jQuery events for Vue to interact with jQuery.
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index a0585fed2fc..5552d5d37b4 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -88,12 +88,12 @@ Until GitLab has eliminated most of these inefficiencies or the use of
NFS is discontinued for Git data, Rugged implementations of some of the
most commonly-used RPCs can be enabled via feature flags:
-* `rugged_find_commit`
-* `rugged_get_tree_entries`
-* `rugged_tree_entry`
-* `rugged_commit_is_ancestor`
-* `rugged_commit_tree_entry`
-* `rugged_list_commits_by_oid`
+- `rugged_find_commit`
+- `rugged_get_tree_entries`
+- `rugged_tree_entry`
+- `rugged_commit_is_ancestor`
+- `rugged_commit_tree_entry`
+- `rugged_list_commits_by_oid`
A convenience Rake task can be used to enable or disable these flags
all together. To enable:
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index ce310672dad..17462887162 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -135,7 +135,7 @@ There is also and alternative method to [translate messages from validation erro
### Interpolation
Placeholders in translated text should match the code style of the respective source file.
-For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript.
+For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make sure to [avoid splitting sentences when adding links](#avoid-splitting-sentences-when-adding-links).
- In Ruby/HAML:
@@ -267,20 +267,41 @@ should be externalized as follows:
This also applies when using links in between translated sentences, otherwise these texts are not translatable in certain languages.
-Instead of:
+- In Ruby/HAML, instead of:
+
+ ```haml
+ - zones_link = link_to(s_('ClusterIntegration|zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
+ ```
+
+ Set the link starting and ending HTML fragments as variables like so:
+
+ ```haml
+ - zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
+ - zones_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: zones_link_url }
+ = s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe % { zones_link_start: zones_link_start, zones_link_end: '</a>'.html_safe }
+ ```
-```haml
-- zones_link = link_to(s_('ClusterIntegration|zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
-= s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
-```
+- In JavaScript, instead of:
-Set the link starting and ending HTML fragments as variables like so:
+ ```js
+ {{
+ sprintf(s__("ClusterIntegration|Learn more about %{link}"), {
+ link: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">zones</a>'
+ })
+ }}
+ ```
-```haml
-- zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
-- zones_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: zones_link_url }
-= s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe % { zones_link_start: zones_link_start, zones_link_end: '</a>'.html_safe }
-```
+ Set the link starting and ending HTML fragments as variables like so:
+
+ ```js
+ {{
+ sprintf(s__("ClusterIntegration|Learn more about %{linkStart}zones%{linkEnd}"), {
+ linkStart: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">'
+ linkEnd: '</a>',
+ })
+ }}
+ ```
The reasoning behind this is that in some languages words change depending on context. For example in Japanese は is added to the subject of a sentence and を to the object. This is impossible to translate correctly if we extract individual words from the sentence.
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 71e3b7740cb..448d9fd01c4 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -327,7 +327,7 @@ However, if a spec makes direct Redis calls, it should mark itself with the
`:clean_gitlab_redis_queues` traits as appropriate.
Sidekiq jobs are typically not run in specs, but this behaviour can be altered
-in each spec through the use of `Sidekiq::Testing.inline!` blocks. Any spec that
+in each spec through the use of `perform_enqueued_jobs` blocks. Any spec that
causes Sidekiq jobs to be pushed to Redis should use the `:sidekiq` trait, to
ensure that they are removed once the spec completes.
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 63b7b97c32f..ae40d628717 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -257,7 +257,7 @@ find a way to limit it to only us.**
## Other resources
-* [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing)
+- [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing)
[charts-1068]: https://gitlab.com/charts/gitlab/issues/1068
[gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587
diff --git a/doc/install/openshift_and_gitlab/index.md b/doc/install/openshift_and_gitlab/index.md
index 18981c43464..45d07ec5d11 100644
--- a/doc/install/openshift_and_gitlab/index.md
+++ b/doc/install/openshift_and_gitlab/index.md
@@ -145,12 +145,12 @@ Login successful.
You have access to the following projects and can switch between them with 'oc project <projectname>':
- * cockpit
- * default (current)
- * delete
- * openshift
- * openshift-infra
- * sample
+- cockpit
+- default (current)
+- delete
+- openshift
+- openshift-infra
+- sample
Using project "default".
```
diff --git a/doc/integration/README.md b/doc/integration/README.md
index f74da97119a..135952a1b08 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -13,10 +13,10 @@ See the documentation below for details on how to configure these services.
- [Auth0 OmniAuth](auth0.md) Enable the Auth0 OmniAuth provider
- [Bitbucket](bitbucket.md) Import projects from Bitbucket.org and login to your GitLab instance with your Bitbucket.org account
- [CAS](cas.md) Configure GitLab to sign in using CAS
-- [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc.
+- [External issue tracker](external-issue-tracker.md) Redmine, Jira, etc.
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [Jenkins](jenkins.md) Integrate with the Jenkins CI
-- [JIRA](../user/project/integrations/jira.md) Integrate with the JIRA issue tracker
+- [Jira](../user/project/integrations/jira.md) Integrate with the Jira issue tracker
- [Kerberos](kerberos.md) Integrate with Kerberos
- [LDAP](ldap.md) Set up sign in via LDAP
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index a2f38a2fcdf..ea2bdc8a96d 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -456,7 +456,7 @@ Here are some common pitfalls and how to overcome them:
See [Elasticsearch Index Scopes](elasticsearch.md#elasticsearch-index-scopes) for more information on searching for specific types of data.
-- **I indexed all the repositories but then switched elastic search servers and now I can't find anything**
+- **I indexed all the repositories but then switched Elasticsearch servers and now I can't find anything**
You will need to re-run all the rake tasks to re-index the database, repositories, and wikis.
diff --git a/doc/integration/jenkins_deprecated.md b/doc/integration/jenkins_deprecated.md
index 8001c5dbd83..eae705c9637 100644
--- a/doc/integration/jenkins_deprecated.md
+++ b/doc/integration/jenkins_deprecated.md
@@ -8,13 +8,13 @@ Please use documentation for the new [Jenkins CI service](jenkins.md).
Integration includes:
-* Trigger Jenkins build after push to repo
-* Show build status on Merge Request page
+- Trigger Jenkins build after push to repo
+- Show build status on Merge Request page
Requirements:
-* [Jenkins GitLab Hook plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
-* git clone access for Jenkins from GitLab repo (via ssh key)
+- [Jenkins GitLab Hook plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
+- git clone access for Jenkins from GitLab repo (via ssh key)
## Jenkins
diff --git a/doc/push_rules/push_rules.md b/doc/push_rules/push_rules.md
index b2d626a0a74..2142f5a5f69 100644
--- a/doc/push_rules/push_rules.md
+++ b/doc/push_rules/push_rules.md
@@ -26,11 +26,11 @@ Every push rule could have its own use case, but let's consider some examples.
Let's assume you have the following requirements for your workflow:
-- every commit should reference a JIRA issue, for example: `Refactored css. Fixes JIRA-123.`
+- every commit should reference a Jira issue, for example: `Refactored css. Fixes JIRA-123.`
- users should not be able to remove git tags with `git push`
All you need to do is write a simple regular expression that requires the mention
-of a JIRA issue in the commit message, like `JIRA\-\d+`.
+of a Jira issue in the commit message, like `JIRA\-\d+`.
Now when a user tries to push a commit with a message `Bugfix`, their push will
be declined. Only pushing commits with messages like `Bugfix according to JIRA-123`
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index c7aa22b11f8..092b4375208 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -18,16 +18,16 @@ installed on your system.
If you installed GitLab:
-- Using the Omnibus package, you're all set.
-- From source, make sure `rsync` is installed:
+- Using the Omnibus package, you're all set.
+- From source, make sure `rsync` is installed:
- ```sh
- # Debian/Ubuntu
- sudo apt-get install rsync
+ ```sh
+ # Debian/Ubuntu
+ sudo apt-get install rsync
- # RHEL/CentOS
- sudo yum install rsync
- ```
+ # RHEL/CentOS
+ sudo yum install rsync
+ ```
### Tar
@@ -269,17 +269,17 @@ For Omnibus GitLab packages:
1. Add the following to `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['backup_upload_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-west-1',
- 'aws_access_key_id' => 'AKIAKIAKI',
- 'aws_secret_access_key' => 'secret123'
- # If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key
- # 'use_iam_profile' => true
- }
- gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
- ```
+ ```ruby
+ gitlab_rails['backup_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-west-1',
+ 'aws_access_key_id' => 'AKIAKIAKI',
+ 'aws_secret_access_key' => 'secret123'
+ # If using an IAM Profile, don't configure aws_access_key_id & aws_secret_access_key
+ # 'use_iam_profile' => true
+ }
+ gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
+ ```
1. [Reconfigure GitLab] for the changes to take effect
@@ -289,16 +289,16 @@ This example can be used for a bucket in Amsterdam (AMS3).
1. Add the following to `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['backup_upload_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'ams3',
- 'aws_access_key_id' => 'AKIAKIAKI',
- 'aws_secret_access_key' => 'secret123',
- 'endpoint' => 'https://ams3.digitaloceanspaces.com'
- }
- gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
- ```
+ ```ruby
+ gitlab_rails['backup_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'ams3',
+ 'aws_access_key_id' => 'AKIAKIAKI',
+ 'aws_secret_access_key' => 'secret123',
+ 'endpoint' => 'https://ams3.digitaloceanspaces.com'
+ }
+ gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
+ ```
1. [Reconfigure GitLab] for the changes to take effect
@@ -321,31 +321,31 @@ For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
- ```yaml
- backup:
- # snip
- upload:
- # Fog storage connection settings, see http://fog.io/storage/ .
- connection:
- provider: AWS
- region: eu-west-1
- aws_access_key_id: AKIAKIAKI
- aws_secret_access_key: 'secret123'
- # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty
- # ie. aws_access_key_id: ''
- # use_iam_profile: 'true'
- # The remote 'directory' to store your backups. For S3, this would be the bucket name.
- remote_directory: 'my.s3.bucket'
- # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
- # encryption: 'AES256'
- # Turns on AWS Server-Side Encryption with Amazon Customer-Provided Encryption Keys for backups, this is optional
- # This should be set to the base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data.
- # 'encryption' must also be set in order for this to have any effect.
- # To avoid storing the key on disk, the key can also be specified via the `GITLAB_BACKUP_ENCRYPTION_KEY` environment variable.
- # encryption_key: '<base64 key>'
- # Specifies Amazon S3 storage class to use for backups, this is optional
- # storage_class: 'STANDARD'
- ```
+ ```yaml
+ backup:
+ # snip
+ upload:
+ # Fog storage connection settings, see http://fog.io/storage/ .
+ connection:
+ provider: AWS
+ region: eu-west-1
+ aws_access_key_id: AKIAKIAKI
+ aws_secret_access_key: 'secret123'
+ # If using an IAM Profile, leave aws_access_key_id & aws_secret_access_key empty
+ # ie. aws_access_key_id: ''
+ # use_iam_profile: 'true'
+ # The remote 'directory' to store your backups. For S3, this would be the bucket name.
+ remote_directory: 'my.s3.bucket'
+ # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
+ # encryption: 'AES256'
+ # Turns on AWS Server-Side Encryption with Amazon Customer-Provided Encryption Keys for backups, this is optional
+ # This should be set to the base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data.
+ # 'encryption' must also be set in order for this to have any effect.
+ # To avoid storing the key on disk, the key can also be specified via the `GITLAB_BACKUP_ENCRYPTION_KEY` environment variable.
+ # encryption_key: '<base64 key>'
+ # Specifies Amazon S3 storage class to use for backups, this is optional
+ # storage_class: 'STANDARD'
+ ```
1. [Restart GitLab] for the changes to take effect
@@ -417,14 +417,14 @@ For Omnibus GitLab packages:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['backup_upload_connection'] = {
- 'provider' => 'Google',
- 'google_storage_access_key_id' => 'Access Key',
- 'google_storage_secret_access_key' => 'Secret'
- }
- gitlab_rails['backup_upload_remote_directory'] = 'my.google.bucket'
- ```
+ ```ruby
+ gitlab_rails['backup_upload_connection'] = {
+ 'provider' => 'Google',
+ 'google_storage_access_key_id' => 'Access Key',
+ 'google_storage_secret_access_key' => 'Secret'
+ }
+ gitlab_rails['backup_upload_remote_directory'] = 'my.google.bucket'
+ ```
1. [Reconfigure GitLab] for the changes to take effect
@@ -434,15 +434,15 @@ For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
- ```yaml
- backup:
- upload:
- connection:
- provider: 'Google'
- google_storage_access_key_id: 'Access Key'
- google_storage_secret_access_key: 'Secret'
- remote_directory: 'my.google.bucket'
- ```
+ ```yaml
+ backup:
+ upload:
+ connection:
+ provider: 'Google'
+ google_storage_access_key_id: 'Access Key'
+ google_storage_secret_access_key: 'Secret'
+ remote_directory: 'my.google.bucket'
+ ```
1. [Restart GitLab] for the changes to take effect
@@ -477,16 +477,16 @@ For Omnibus GitLab packages:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['backup_upload_connection'] = {
- :provider => 'Local',
- :local_root => '/mnt/backups'
- }
+ ```ruby
+ gitlab_rails['backup_upload_connection'] = {
+ :provider => 'Local',
+ :local_root => '/mnt/backups'
+ }
- # The directory inside the mounted folder to copy backups to
- # Use '.' to store them in the root directory
- gitlab_rails['backup_upload_remote_directory'] = 'gitlab_backups'
- ```
+ # The directory inside the mounted folder to copy backups to
+ # Use '.' to store them in the root directory
+ gitlab_rails['backup_upload_remote_directory'] = 'gitlab_backups'
+ ```
1. [Reconfigure GitLab] for the changes to take effect.
@@ -496,17 +496,17 @@ For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
- ```yaml
- backup:
- upload:
- # Fog storage connection settings, see http://fog.io/storage/ .
- connection:
- provider: Local
- local_root: '/mnt/backups'
- # The directory inside the mounted folder to copy backups to
- # Use '.' to store them in the root directory
- remote_directory: 'gitlab_backups'
- ```
+ ```yaml
+ backup:
+ upload:
+ # Fog storage connection settings, see http://fog.io/storage/ .
+ connection:
+ provider: Local
+ local_root: '/mnt/backups'
+ # The directory inside the mounted folder to copy backups to
+ # Use '.' to store them in the root directory
+ remote_directory: 'gitlab_backups'
+ ```
1. [Restart GitLab] for the changes to take effect.
@@ -521,9 +521,9 @@ For Omnibus GitLab packages:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['backup_archive_permissions'] = 0644 # Makes the backup archives world-readable
- ```
+ ```ruby
+ gitlab_rails['backup_archive_permissions'] = 0644 # Makes the backup archives world-readable
+ ```
1. [Reconfigure GitLab] for the changes to take effect.
@@ -533,10 +533,10 @@ For installations from source:
1. Edit `/home/git/gitlab/config/gitlab.yml`:
- ```yaml
- backup:
- archive_permissions: 0644 # Makes the backup archives world-readable
- ```
+ ```yaml
+ backup:
+ archive_permissions: 0644 # Makes the backup archives world-readable
+ ```
1. [Restart GitLab] for the changes to take effect.
@@ -550,10 +550,10 @@ For Omnibus GitLab packages:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- ## Limit backup lifetime to 7 days - 604800 seconds
- gitlab_rails['backup_keep_time'] = 604800
- ```
+ ```ruby
+ ## Limit backup lifetime to 7 days - 604800 seconds
+ gitlab_rails['backup_keep_time'] = 604800
+ ```
1. [Reconfigure GitLab] for the changes to take effect.
@@ -586,11 +586,11 @@ For installations from source:
1. Edit `home/git/gitlab/config/gitlab.yml`:
- ```yaml
- backup:
- ## Limit backup lifetime to 7 days - 604800 seconds
- keep_time: 604800
- ```
+ ```yaml
+ backup:
+ ## Limit backup lifetime to 7 days - 604800 seconds
+ keep_time: 604800
+ ```
1. [Restart GitLab] for the changes to take effect.
@@ -840,13 +840,13 @@ columns containing sensitive information. If the key is lost, GitLab will be
unable to decrypt those columns. This will break a wide range of functionality,
including (but not restricted to):
-* [CI/CD variables](../ci/variables/README.md)
-* [Kubernetes / GCP integration](../user/project/clusters/index.md)
-* [Custom Pages domains](../user/project/pages/getting_started_part_three.md)
-* [Project error tracking](../user/project/operations/error_tracking.md)
-* [Runner authentication](../ci/runners/README.md)
-* [Project mirroring](../workflow/repository_mirroring.md)
-* [Web hooks](../user/project/integrations/webhooks.md)
+- [CI/CD variables](../ci/variables/README.md)
+- [Kubernetes / GCP integration](../user/project/clusters/index.md)
+- [Custom Pages domains](../user/project/pages/getting_started_part_three.md)
+- [Project error tracking](../user/project/operations/error_tracking.md)
+- [Runner authentication](../ci/runners/README.md)
+- [Project mirroring](../workflow/repository_mirroring.md)
+- [Web hooks](../user/project/integrations/webhooks.md)
In cases like CI/CD variables and Runner authentication, you might
experience some unexpected behavior such as:
@@ -865,72 +865,71 @@ backup beforehand.
#### Reset CI/CD variables
-1. Enter the DB console:
+1. Enter the DB console:
- For Omnibus GitLab packages:
+ For Omnibus GitLab packages:
- ```sh
- sudo gitlab-rails dbconsole
- ```
+ ```sh
+ sudo gitlab-rails dbconsole
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production
- ```
+ ```sh
+ sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production
+ ```
-1. Check the `ci_group_variables` and `ci_variables` tables:
+1. Check the `ci_group_variables` and `ci_variables` tables:
- ```sql
- SELECT * FROM public."ci_group_variables";
- SELECT * FROM public."ci_variables";
- ```
+ ```sql
+ SELECT * FROM public."ci_group_variables";
+ SELECT * FROM public."ci_variables";
+ ```
- Those are the variables that you need to delete.
+ Those are the variables that you need to delete.
-1. Drop the table:
+1. Drop the table:
- ```sql
- DELETE FROM ci_group_variables;
- DELETE FROM ci_variables;
- ```
+ ```sql
+ DELETE FROM ci_group_variables;
+ DELETE FROM ci_variables;
+ ```
1. You may need to reconfigure or restart GitLab for the changes to take
effect.
-
#### Reset Runner registration tokens
-1. Enter the DB console:
+1. Enter the DB console:
- For Omnibus GitLab packages:
+ For Omnibus GitLab packages:
- ```sh
- sudo gitlab-rails dbconsole
- ```
+ ```sh
+ sudo gitlab-rails dbconsole
+ ```
- For installations from source:
+ For installations from source:
- ```sh
- sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production
- ```
+ ```sh
+ sudo -u git -H bundle exec rails dbconsole RAILS_ENV=production
+ ```
1. Clear all the tokens for projects, groups, and the whole instance:
- CAUTION: **Caution:**
- The last UPDATE operation will stop the runners being able to pick up
- new jobs. You must register new runners.
-
- ```sql
- -- Clear project tokens
- UPDATE projects SET runners_token = null, runners_token_encrypted = null;
- -- Clear group tokens
- UPDATE namespaces SET runners_token = null, runners_token_encrypted = null;
- -- Clear instance tokens
- UPDATE application_settings SET runners_registration_token_encrypted = null;
- -- Clear runner tokens
- UPDATE ci_runners SET token = null, token_encrypted = null;
- ```
+ CAUTION: **Caution:**
+ The last UPDATE operation will stop the runners being able to pick up
+ new jobs. You must register new runners.
+
+ ```sql
+ -- Clear project tokens
+ UPDATE projects SET runners_token = null, runners_token_encrypted = null;
+ -- Clear group tokens
+ UPDATE namespaces SET runners_token = null, runners_token_encrypted = null;
+ -- Clear instance tokens
+ UPDATE application_settings SET runners_registration_token_encrypted = null;
+ -- Clear runner tokens
+ UPDATE ci_runners SET token = null, token_encrypted = null;
+ ```
A similar strategy can be employed for the remaining features - by removing the
data that cannot be decrypted, GitLab can be brought back into working order,
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index f5c788af578..f880f31c39e 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -92,3 +92,48 @@ I, [2018-08-02T10:26:47.598424 #45087] INFO -- : Looking for orphaned remote up
I, [2018-08-02T10:26:47.753131 #45087] INFO -- : Moved to lost and found: @hashed/6b/DSC_6152.JPG -> lost_and_found/@hashed/6b/DSC_6152.JPG
I, [2018-08-02T10:26:47.764356 #45087] INFO -- : Moved to lost and found: @hashed/79/02/7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451/711491b29d3eb08837798c4909e2aa4d/DSC00314.jpg -> lost_and_found/@hashed/79/02/7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451/711491b29d3eb08837798c4909e2aa4d/DSC00314.jpg
```
+
+## Remove orphan artifact files
+
+When you notice there are more job artifacts files on disk than there
+should be, you can run:
+
+```shell
+gitlab-rake gitlab:cleanup:orphan_job_artifact_files
+```
+
+This command:
+
+- Scans through the entire artifacts folder.
+- Checks which files still have a record in the database.
+- If no database record is found, the file is deleted from disk.
+
+By default, this task does not delete anything but shows what it can
+delete. Run the command with `DRY_RUN=false` if you actually want to
+delete the files:
+
+```shell
+gitlab-rake gitlab:cleanup:orphan_job_artifact_files DRY_RUN=false
+```
+
+You can also limit the number of files to delete with `LIMIT`:
+
+```shell
+gitlab-rake gitlab:cleanup:orphan_job_artifact_files LIMIT=100`
+```
+
+This will only delete up to 100 files from disk. You can use this to
+delete a small set for testing purposes.
+
+If you provide `DEBUG=1`, you'll see the full path of every file that
+is detected as being an orphan.
+
+If `ionice` is installed, the tasks uses it to ensure the command is
+not causing too much load on the disk. You can configure the niceness
+level with `NICENESS`. Below are the valid levels, but consult
+`man 1 ionice` to be sure.
+
+- `0` or `None`
+- `1` or `Realtime`
+- `2` or `Best-effort` (default)
+- `3` or `Idle`
diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md
index df3dab118b2..2c6ae0749dd 100644
--- a/doc/raketasks/web_hooks.md
+++ b/doc/raketasks/web_hooks.md
@@ -2,42 +2,54 @@
## Add a webhook for **ALL** projects:
- # omnibus-gitlab
- sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook"
- # source installations
- bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" RAILS_ENV=production
+```sh
+# omnibus-gitlab
+sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook"
+# source installations
+bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" RAILS_ENV=production
+```
## Add a webhook for projects in a given **NAMESPACE**:
- # omnibus-gitlab
- sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme
- # source installations
- bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
+```sh
+# omnibus-gitlab
+sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme
+# source installations
+bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
+```
## Remove a webhook from **ALL** projects using:
- # omnibus-gitlab
- sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook"
- # source installations
- bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" RAILS_ENV=production
+```sh
+# omnibus-gitlab
+sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook"
+# source installations
+bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" RAILS_ENV=production
+```
## Remove a webhook from projects in a given **NAMESPACE**:
- # omnibus-gitlab
- sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme
- # source installations
- bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
+```sh
+# omnibus-gitlab
+sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme
+# source installations
+bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
+```
## List **ALL** webhooks:
- # omnibus-gitlab
- sudo gitlab-rake gitlab:web_hook:list
- # source installations
- bundle exec rake gitlab:web_hook:list RAILS_ENV=production
+```sh
+# omnibus-gitlab
+sudo gitlab-rake gitlab:web_hook:list
+# source installations
+bundle exec rake gitlab:web_hook:list RAILS_ENV=production
+```
## List the webhooks from projects in a given **NAMESPACE**:
- # omnibus-gitlab
- sudo gitlab-rake gitlab:web_hook:list NAMESPACE=acme
- # source installations
- bundle exec rake gitlab:web_hook:list NAMESPACE=acme RAILS_ENV=production
+```sh
+# omnibus-gitlab
+sudo gitlab-rake gitlab:web_hook:list NAMESPACE=acme
+# source installations
+bundle exec rake gitlab:web_hook:list NAMESPACE=acme RAILS_ENV=production
+```
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index fa4b0d1fb09..8695b5d2194 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -53,8 +53,9 @@ For more information on how to use these options check out
The following settings can be configured:
- `enabled`: By default this is set to `false`. Set this to `true` to enable Rack Attack.
-- `ip_whitelist`: Whitelist any IPs from being blocked. They must be formatted as strings within a ruby array.
- For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3"]`.
+- `ip_whitelist`: Whitelist any IPs from being blocked. They must be formatted as strings within a Ruby array.
+ CIDR notation is supported in GitLab v12.1 and up.
+ For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3", "192.168.0.1/24"]`.
- `maxretry`: The maximum amount of times a request can be made in the
specified time.
- `findtime`: The maximum amount of time that failed requests can count against an IP
diff --git a/doc/topics/application_development_platform/index.md b/doc/topics/application_development_platform/index.md
index e8960a76dd9..8742606479d 100644
--- a/doc/topics/application_development_platform/index.md
+++ b/doc/topics/application_development_platform/index.md
@@ -1,12 +1,21 @@
# Application Development Platform
-The GitLab Application Development Platform refers to the set of GitLab features that can be used by operations teams to
-provide a full development environment to internal software development teams.
+The GitLab Application Development Platform refers to the set of GitLab features used to create, configure, and manage
+a complete software development environment. It provides development, operations, and security teams with a robust feature set aimed at supporting best practices out of the box.
## Overview
-The GitLab Application Development Platform aims to reduce and even eliminate the time it takes for an Operations team
-to provide a full environment for software developers. It comprises the following high-level elements:
+The GitLab Application Development Platform aims to:
+
+- Reduce and even eliminate the time it takes for an Operations team
+ to provide a full environment for software developers.
+- Get developers up and running fast so they can focus on writing
+ great applications with a robust development feature set.
+- Provide best-of-breed security features so that applications developed
+ with GitLab are not affected by vulnerabilities that may lead to security
+ problems and unintended use.
+
+It is comprised of the following high-level elements:
1. Compute
1. Build, test, and deploy a wide range of applications
diff --git a/doc/university/README.md b/doc/university/README.md
index 1d2c0f2068a..9d861460618 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -181,7 +181,7 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
### 3.9. Integrations
-1. [How to Integrate JIRA and Jenkins with GitLab - Video](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415)
+1. [How to Integrate Jira and Jenkins with GitLab - Video](https://gitlabmeetings.webex.com/gitlabmeetings/ldr.php?RCID=44b548147a67ab4d8a62274047146415)
1. [How to Integrate Jira with GitLab](../user/project/integrations/jira.md)
1. [How to Integrate Jenkins with GitLab](../integration/jenkins.md)
1. [How to Integrate Bamboo with GitLab](../user/project/integrations/bamboo.md)
diff --git a/doc/university/support/README.md b/doc/university/support/README.md
index 35e65b60768..2c6e52acfde 100644
--- a/doc/university/support/README.md
+++ b/doc/university/support/README.md
@@ -80,7 +80,7 @@ Our integrations add great value to GitLab. User questions often relate to integ
- Learn about our Integrations (specially, not only):
- [LDAP](../../integration/ldap.md)
- - [JIRA](../../project_services/jira.md)
+ - [Jira](../../project_services/jira.md)
- [Jenkins](../../integration/jenkins.md)
- [SAML](../../integration/saml.md)
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index 428377adb19..7ae716d2cb3 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -25,14 +25,14 @@ Branch names use the format `major-minor-stable-ee` for Enterprise Edition, and
`major-minor-stable` for Community Edition. For example, for 11.8.0 you would
use the following branches:
-* Enterprise Edition: `11-8-stable-ee`
-* Community Edition: `11-8-stable`
+- Enterprise Edition: `11-8-stable-ee`
+- Community Edition: `11-8-stable`
### 0. Backup
Make a backup just in case something goes wrong:
-```bash
+```sh
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
@@ -42,13 +42,13 @@ privileges to the GitLab user on the database version.
### 1. Stop server
-```bash
+```sh
sudo service gitlab stop
```
### 2. Get the EE code
-```bash
+```sh
cd /home/git/gitlab
sudo -u git -H git remote add -f ee https://gitlab.com/gitlab-org/gitlab-ee.git
sudo -u git -H git checkout EE_BRANCH
@@ -56,7 +56,7 @@ sudo -u git -H git checkout EE_BRANCH
### 3. Install libs, migrations, etc.
-```bash
+```sh
cd /home/git/gitlab
# MySQL installations (note: the line below states '--without postgres')
@@ -80,7 +80,7 @@ document linked above and enable the indexer usage in the GitLab admin settings.
### 5. Start application
-```bash
+```sh
sudo service gitlab start
sudo service nginx restart
```
@@ -89,13 +89,13 @@ sudo service nginx restart
Check if GitLab and its environment are configured correctly:
-```bash
+```sh
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
```
To make sure you didn't miss anything run a more thorough check with:
-```bash
+```sh
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
@@ -105,14 +105,14 @@ If all items are green, then congratulations upgrade complete!
### 1. Revert the code to the previous version
-```bash
+```sh
cd /home/git/gitlab
sudo -u git -H git checkout CE_BRANCH
```
### 2. Restore from the backup
-```bash
+```sh
cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
```
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index fa60ee96cf7..d2947ae3371 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -21,7 +21,7 @@ The Admin Area is made up of the following sections:
| Section | Description |
|:------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Overview](#overview-section) | View your GitLab [Dashboard](#admin-dashboard), and administer [projects](#administering-projects), [users](#administering-users), [groups](#administering-groups), [jobs](#administering-jobs), [Runners](#administering-runners), and [Gitaly servers](#administering-gitaly-servers). |
-| Monitoring | View GitLab system information, and information on background jobs, logs, [health checks](monitoring/health_check.md), request profiles, and audit logs. |
+| Monitoring | View GitLab [system information](#system-info), and information on [background jobs](#background-jobs), [logs](#logs), [health checks](monitoring/health_check.md), [requests profiles](#requests-profiles), and [audit logs](#audit-log-premium-only). |
| Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. |
| System Hooks | Configure [system hooks](../../system_hooks/system_hooks.md) for many events. |
| Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. |
@@ -229,3 +229,66 @@ For each Gitaly server, the following details are listed:
| Server version | Gitaly version |
| Git version | Version of Git installed on the Gitaly server |
| Up to date | Indicates if the Gitaly server version is the latest version available. A green dot indicates the server is up to date. |
+
+## Monitoring section
+
+The following topics document the **Monitoring** section of the Admin Area.
+
+### System Info
+
+The **System Info** page provides the following statistics:
+
+| Field | Description |
+| :----------- | :---------- |
+| CPU | Number of CPU cores available |
+| Memory Usage | Memory in use, and total memory available |
+| Disk Usage | Disk space in use, and total disk space available |
+| Uptime | Approximate uptime of the GitLab instance |
+
+These statistics are updated only when you navigate to the **System Info** page, or you refresh the page in your browser.
+
+### Background Jobs
+
+The **Background Jobs** page displays the Sidekiq dashboard. Sidekiq is used by GitLab to
+perform processing in the background.
+
+The Sidekiq dashboard consists of the following elements:
+
+- A tab per jobs' status.
+- A breakdown of background job statistics.
+- A live graph of **Processed** and **Failed** jobs, with a selectable polling interval.
+- An historical graph of **Processed** and **Failed** jobs, with a selectable time span.
+- Redis statistics, including:
+ - Version number
+ - Uptime, measured in days
+ - Number of connections
+ - Current memory usage, measured in MB
+ - Peak memory usage, measured in MB
+
+### Logs
+
+The **Logs** page provides access to the following log files:
+
+| Log file | Contents |
+| :---------------------- | :------- |
+| `application.log` | GitLab user activity |
+| `githost.log` | Failed GitLab interaction with Git repositories |
+| `production.log` | Requests received from Unicorn, and the actions taken to serve those requests |
+| `sidekiq.log` | Background jobs |
+| `repocheck.log` | Repository activity |
+| `integrations_json.log` | Activity between GitLab and integrated systems |
+| `kubernetes.log` | Kubernetes activity |
+
+The contents of these log files can be useful when troubleshooting a problem. Access is available to GitLab admins, without requiring direct access to the log files.
+
+For details of these log files and their contents, see [Log system](../../administration/logs.md).
+
+The content of each log file is listed in chronological order. To minimize performance issues, a maximum 2000 lines of each log file are shown.
+
+### Requests Profiles
+
+The **Requests Profiles** page contains the token required for profiling. For more details, see [Request Profiling](../../administration/monitoring/performance/request_profiling.md).
+
+### Audit Log **[PREMIUM ONLY]**
+
+The **Audit Log** page lists changes made within the GitLab server. With this information you can control, analyze, and track every change.
diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index f80d4e9d2aa..35e7b6fb541 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -41,42 +41,51 @@ The readiness and liveness probes will provide a report of system health in JSON
```json
{
- "queues_check" : {
- "status" : "ok"
+ "db_check":{
+ "status":"ok"
},
- "redis_check" : {
- "status" : "ok"
+ "redis_check":{
+ "status":"ok"
},
- "shared_state_check" : {
- "status" : "ok"
+ "cache_check":{
+ "status":"ok"
},
- "db_check" : {
- "status" : "ok"
+ "queues_check":{
+ "status":"ok"
},
- "cache_check" : {
- "status" : "ok"
+ "shared_state_check":{
+ "status":"ok"
+ },
+ "gitaly_check":{
+ "status":"ok",
+ "labels":{
+ "shard":"default"
+ }
+ }
}
-}
```
`liveness` probe example output:
```json
{
- "cache_check" : {
- "status" : "ok"
+ "db_check":{
+ "status":"ok"
+ },
+ "redis_check":{
+ "status":"ok"
},
- "db_check" : {
- "status" : "ok"
+ "cache_check":{
+ "status":"ok"
},
- "redis_check" : {
- "status" : "ok"
+ "queues_check":{
+ "status":"ok"
},
- "queues_check" : {
- "status" : "ok"
+ "shared_state_check":{
+ "status":"ok"
},
- "shared_state_check" : {
- "status" : "ok"
+ "gitaly_check":{
+ "status":"ok"
}
}
```
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index 4a2fb1d7190..9dfbe326f1d 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -206,6 +206,11 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
+## Vulnerabilities database update
+
+For more information about the vulnerabilities database update, check the
+[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
+
## Troubleshooting
### docker: Error response from daemon: failed to copy xattrs
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index a722aa88f9d..2283efe3a44 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -259,3 +259,8 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
+
+## Vulnerabilities database update
+
+For more information about the vulnerabilities database update, check the
+[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index ea8b96eb24d..9145e034dcb 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -404,6 +404,11 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
+## Vulnerabilities database update
+
+For more information about the vulnerabilities database update, check the
+[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
+
## Dependency List
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 679847b76d7..69fa1ec5da6 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -10,7 +10,7 @@ high-level view on projects and groups, and start remediation processes when nee
GitLab can scan and report any vulnerabilities found in your project.
-| Secure scanning tools | Description |
+| Secure scanning tool | Description |
|:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------|
| [Container Scanning](container_scanning/index.md) **[ULTIMATE]** | Scan Docker containers for known vulnerabilities. |
| [Dependency Scanning](dependency_scanning/index.md) **[ULTIMATE]** | Analyze your dependencies for known vulnerabilities. |
@@ -19,6 +19,29 @@ GitLab can scan and report any vulnerabilities found in your project.
| [Security Dashboard](security_dashboard/index.md) **[ULTIMATE]** | View vulnerabilities in all your projects and groups. |
| [Static Application Security Testing (SAST)](sast/index.md) **[ULTIMATE]** | Analyze source code for known vulnerabilities. |
+## Maintenance and update of the vulnerabilities database
+
+The various scanning tools and the vulnerabilities database are updated regularly.
+
+| Secure scanning tool | Vulnerabilities database updates |
+|:-------------------------------------------------------------|-------------------------------------------|
+| [Container Scanning](container_scanning/index.md) | Uses `clair` underneath and the latest `clair-db` version is used for each job run by running the [`latest` docker image tag](https://gitlab.com/gitlab-org/gitlab-ee/blob/438a0a56dc0882f22bdd82e700554525f552d91b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml#L37). The `clair-db` database [is updated daily according to the author](https://github.com/arminc/clair-local-scan#clair-server-or-local). |
+| [Dependency Scanning](dependency_scanning/index.md) | Relies on `bundler-audit` (for Rubygems), `retire.js` (for NPM packages) and `gemnasium` (GitLab's own tool for all libraries). `bundler-audit` and `retire.js` both fetch their vulnerabilities data from GitHub repositories, so vulnerabilities added to `ruby-advisory-db` and `retire.js` are immediately available. The tools themselves are updated once per month if there's a new version. The [Gemnasium DB](https://gitlab.com/gitlab-org/security-products/gemnasium-db) is updated at least once a week. |
+| [Dynamic Application Security Testing (DAST)](dast/index.md) | Updated weekly on Sundays. The underlying tool, `zaproxy`, downloads fresh rules at startup. |
+| [Static Application Security Testing (SAST)](sast/index.md) | Relies exclusively on [the tools GitLab is wrapping](sast/index.md#supported-languages-and-frameworks). The underlying analyzers are updated at least once per month if a relevant update is available. The vulnerabilities database is updated by the upstream tools. |
+
+You don't have to update GitLab to benefit from the latest vulnerabilities definitions,
+but you may have to in the future.
+
+The security tools are released as Docker images, and the vendored job definitions
+to enable them are using the `x-y-stable` image tags that get overridden each time a new
+release of the tools is pushed. The Docker images are updated to match the
+previous GitLab releases, so they automatically get the latest versions of the
+scanning tools without the user having to do anything.
+
+This workflow comes with some drawbacks and there's a
+[plan to change this](https://gitlab.com/gitlab-org/gitlab-ee/issues/9725).
+
## Interacting with the vulnerabilities
> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 10.8.
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index ec3f7fbde76..9074ac3f4a1 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -269,7 +269,7 @@ it highlighted:
"url": "https://cwe.mitre.org/data/definitions/330.html"
}
]
- },
+ },
{
"category": "sast",
"message": "Probable insecure usage of temp file/directory.",
@@ -296,7 +296,7 @@ it highlighted:
"url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
}
]
- },
+ },
],
"remediations": []
}
@@ -320,7 +320,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on
| `vulnerabilities[].scanner` | A node that describes the analyzer used to find this vulnerability. |
| `vulnerabilities[].scanner.id` | Id of the scanner as a snake_case string. |
| `vulnerabilities[].scanner.name` | Name of the scanner, for display purposes. |
-| `vulnerabilities[].location` | A node that tells where the vulnerability is located. |
+| `vulnerabilities[].location` | A node that tells where the vulnerability is located. |
| `vulnerabilities[].location.file` | Path to the file where the vulnerability is located. Optional. |
| `vulnerabilities[].location.start_line` | The first line of the code affected by the vulnerability. Optional. |
| `vulnerabilities[].location.end_line` | The last line of the code affected by the vulnerability. Optional. |
@@ -330,7 +330,7 @@ the report JSON unless stated otherwise. Presence of optional fields depends on
| `vulnerabilities[].identifiers[].type` | Type of the identifier. Possible values: common identifier types (among `cve`, `cwe`, `osvdb`, and `usn`) or analyzer-dependent ones (e.g., `bandit_test_id` for [Bandit analyzer](https://wiki.openstack.org/wiki/Security/Projects/Bandit)). |
| `vulnerabilities[].identifiers[].name` | Name of the identifier for display purposes. |
| `vulnerabilities[].identifiers[].value` | Value of the identifier for matching purposes. |
-| `vulnerabilities[].identifiers[].url` | URL to identifier's documentation. Optional. |
+| `vulnerabilities[].identifiers[].url` | URL to identifier's documentation. Optional. |
## Secret detection
@@ -363,3 +363,8 @@ vulnerabilities in your groups and projects. Read more about the
Once a vulnerability is found, you can interact with it. Read more on how to
[interact with the vulnerabilities](../index.md#interacting-with-the-vulnerabilities).
+
+## Vulnerabilities database update
+
+For more information about the vulnerabilities database update, check the
+[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index b520c4fb579..f2516c6db0c 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -251,6 +251,7 @@ The applications below can be uninstalled.
| Application | GitLab version | Notes |
| ----------- | -------------- | ----- |
+| JupyterHub | 12.1+ | All data not committed to GitLab will be deleted and cannot be restored. |
| Prometheus | 11.11+ | All data will be deleted and cannot be restored. |
To uninstall an application:
@@ -287,4 +288,3 @@ To avoid installation errors:
kubectl get secrets/tiller-secret -n gitlab-managed-apps -o "jsonpath={.data['ca\.crt']}" | base64 -d > b.pem
diff a.pem b.pem
```
-
diff --git a/doc/user/group/contribution_analytics/index.md b/doc/user/group/contribution_analytics/index.md
index 0da131ab7bd..a555b7723df 100644
--- a/doc/user/group/contribution_analytics/index.md
+++ b/doc/user/group/contribution_analytics/index.md
@@ -54,13 +54,13 @@ Select the desired period from the calendar dropdown.
Contributions per group member are also presented in tabular format. Click a column header to sort the table by that column:
-* Member name
-* Number of pushed events
-* Number of opened issues
-* Number of closed issues
-* Number of opened MRs
-* Number of accepted MRs
-* Number of total contributions
+- Member name
+- Number of pushed events
+- Number of opened issues
+- Number of closed issues
+- Number of opened MRs
+- Number of accepted MRs
+- Number of total contributions
![Contribution analytics contributions table](img/group_stats_table.png)
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 183b12b1c73..7240b8e118b 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -218,6 +218,8 @@ Get an overview of the vulnerabilities of all the projects in a group and its su
## Insights **[ULTIMATE]**
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+
Configure the Insights that matter for your groups or projects, allowing users
to explore data such as:
diff --git a/doc/user/group/insights/index.md b/doc/user/group/insights/index.md
index 1aba5d0986b..e6ba47939b3 100644
--- a/doc/user/group/insights/index.md
+++ b/doc/user/group/insights/index.md
@@ -4,8 +4,7 @@ type: reference, howto
# Insights **[ULTIMATE]**
-> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.9 behind the `insights` feature flag.
-> **Generally Available** (GA) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
Configure the Insights that matter for your groups to explore data such as
triage hygiene, issues created/closed per a given period, average time for merge
diff --git a/doc/user/index.md b/doc/user/index.md
index 1fc4e4c43cf..899026a801f 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -66,7 +66,7 @@ With GitLab Enterprise Edition, you can also:
- Leverage continuous delivery method with [Canary Deployments](project/canary_deployments.md).
- Scan your code for vulnerabilities and [display them in merge requests](application_security/sast/index.md).
-You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
+You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, Jira, and a lot more.
## Projects
@@ -153,7 +153,7 @@ you have quick access to. You can also gather feedback on them through
## Integrations
[Integrate GitLab](../integration/README.md) with your preferred tool,
-such as Trello, JIRA, etc.
+such as Trello, Jira, etc.
## Webhooks
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 16df6d93277..f8eea618c84 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -1115,20 +1115,20 @@ will point the link to `wikis/style` only when the link is inside of a wiki mark
GFM will autolink almost any URL you put into your text:
```markdown
-* https://www.google.com
-* https://google.com/
-* ftp://ftp.us.debian.org/debian/
-* smb://foo/bar/baz
-* irc://irc.freenode.net/gitlab
-* http://localhost:3000
+- https://www.google.com
+- https://google.com/
+- ftp://ftp.us.debian.org/debian/
+- smb://foo/bar/baz
+- irc://irc.freenode.net/gitlab
+- http://localhost:3000
```
-* https://www.google.com
-* https://google.com/
-* ftp://ftp.us.debian.org/debian/
-* smb://foo/bar/baz
-* irc://irc.freenode.net/gitlab
-* http://localhost:3000
+- https://www.google.com
+- https://google.com/
+- ftp://ftp.us.debian.org/debian/
+- smb://foo/bar/baz
+- irc://irc.freenode.net/gitlab
+- http://localhost:3000
### Lists
@@ -1147,7 +1147,7 @@ Examples:
```md
1. First ordered list item
2. Another item
- * Unordered sub-list.
+ - Unordered sub-list.
1. Actual numbers don't matter, just that it's a number
1. Ordered sub-list
1. Next ordered sub-list item
@@ -1160,7 +1160,7 @@ Examples:
1. First ordered list item
2. Another item
- * Unordered sub-list.
+ - Unordered sub-list.
1. Actual numbers don't matter, just that it's a number
1. Ordered sub-list
1. Next ordered sub-list item
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 7af3d4a0ac3..03abef9fc62 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -365,3 +365,8 @@ for details about the pipelines security model.
Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user.
Read through the documentation on [LDAP users permissions](../administration/auth/how_to_configure_ldap_gitlab_ee/index.html) to learn more.
+
+## Project aliases
+
+Project aliases can only be read, created and deleted by a GitLab administrator.
+Read through the documentation on [Project aliases](../user/project/index.md#project-aliases-premium-only) to learn more.
diff --git a/doc/user/profile/account/img/2fa.png b/doc/user/profile/account/img/2fa.png
deleted file mode 100644
index bb464efa685..00000000000
--- a/doc/user/profile/account/img/2fa.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/profile/account/img/2fa_auth.png b/doc/user/profile/account/img/2fa_auth.png
deleted file mode 100644
index 0caaed10805..00000000000
--- a/doc/user/profile/account/img/2fa_auth.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/profile/account/img/2fa_u2f_authenticate.png b/doc/user/profile/account/img/2fa_u2f_authenticate.png
deleted file mode 100644
index ff2e936764d..00000000000
--- a/doc/user/profile/account/img/2fa_u2f_authenticate.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/profile/account/img/2fa_u2f_register.png b/doc/user/profile/account/img/2fa_u2f_register.png
deleted file mode 100644
index 1cc142aa851..00000000000
--- a/doc/user/profile/account/img/2fa_u2f_register.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 26cacbe5545..e3e8c9a0d6d 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -10,17 +10,18 @@ is to know your username and password *and* have access to your one time passwor
## Overview
-> **Note:**
-When you enable 2FA, don't forget to back up your recovery codes.
-
-In addition to one time authenticators (TOTP), GitLab supports U2F (universal 2nd factor) devices as
-the second factor of authentication. Once enabled, in addition to supplying your username and
-password to login, you'll 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 [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
+TIP: **Tip:**
+When you enable 2FA, don't forget to back up your [recovery codes](#recovery-codes)!
+
+In addition to time-based one time passwords (TOTP), GitLab supports U2F
+(universal 2nd factor) devices as the second factor of authentication. Once
+enabled, in addition to supplying your username and password to login, you'll
+be prompted to activate your U2F device (usually by pressing a button on it),
+and it will perform secure authentication on your behalf.
+
+It is highly recommended 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
@@ -30,41 +31,52 @@ or a U2F device.
### Enable 2FA via one time password authenticator
-**In GitLab:**
-
-1. Log in to your GitLab account.
-1. Go to your **Profile Settings**.
-1. Go to **Account**.
-1. Click **Enable Two-factor Authentication**.
-
-![Two-factor setup](img/2fa.png)
-
-**On your phone:**
-
-1. Install a compatible application. We recommend [Google Authenticator]
- \(proprietary\) or [FreeOTP] \(open source\).
-1. In the application, add a new entry in one of two ways:
- - Scan the code with your phone's camera to add the entry automatically.
- - Enter the details provided to add the entry manually.
-
-**In GitLab:**
-
-1. Enter the six-digit pin number from the entry on your phone into the **Pin
- code** field.
-1. Click **Submit**.
+To enable 2FA:
+
+1. **In GitLab:**
+ 1. Log in to your GitLab account.
+ 1. Go to your **Profile Settings**.
+ 1. Go to **Account**.
+ 1. Click **Enable Two-factor Authentication**.
+1. **On your device (usually your phone):**
+ 1. Install a compatible application, like:
+ - [Authenticator](https://mattrubin.me/authenticator/): open source app for iOS devices.
+ - [andOTP](https://github.com/andOTP/andOTP): feature rich open source app for Android which supports PGP encrypted backups.
+ - [FreeOTP](https://freeotp.github.io/): open source app for Android.
+ - [Google Authenticator](https://support.google.com/accounts/answer/1066447?hl=en): proprietary app for iOS and Android.
+ 1. In the application, add a new entry in one of two ways:
+ - Scan the code presented in GitLab with your device's camera to add the
+ entry automatically.
+ - Enter the details provided to add the entry manually.
+1. **In GitLab:**
+ 1. Enter the six-digit pin number from the entry on your device into the **Pin
+ code** field.
+ 1. Click **Submit**.
If the pin you entered was correct, you'll see a message indicating that
Two-Factor Authentication has been enabled, and you'll be presented with a list
-of recovery codes.
+of [recovery codes](#recovery-codes). Make sure you download them and keep them
+in a safe place.
### Enable 2FA via U2F device
-> **Notes:**
->
-> - GitLab officially only supports [Yubikey] U2F devices.
-> - Support for U2F devices was added in GitLab 8.8.
+GitLab officially only supports [YubiKey](https://www.yubico.com/products/yubikey-hardware/)
+U2F devices, but users have successfully used [SoloKeys](https://solokeys.com/).
+
+The U2F workflow is [supported by](https://caniuse.com/#search=U2F) the
+following desktop browsers:
-**In GitLab:**
+- Chrome
+- Edge
+- Firefox (disabled by default)
+- Opera
+
+NOTE: **Note:**
+For Firefox, you can enable the FIDO U2F API in
+[about:config](https://support.mozilla.org/en-US/kb/about-config-editor-firefox).
+Search for `security.webauth.u2f` and double click on it to toggle to `true`.
+
+To set up 2FA with a U2F device:
1. Log in to your GitLab account.
1. Go to your **Profile Settings**.
@@ -77,19 +89,21 @@ of recovery codes.
You will see a message indicating that your device was successfully set up.
Click on **Register U2F Device** to complete the process.
-![Two-Factor U2F Setup](img/2fa_u2f_register.png)
+## Recovery codes
-## Recovery Codes
-
-> **Note:**
+NOTE: **Note:**
Recovery codes are not generated for U2F devices.
-Immediately after successfully enabling two-factor authentication, you'll be prompted to download a set of set recovery codes. Should you ever lose access to your one time password authenticator, you can use one of them to log in to your account. We suggest copying them, printing them, or downloading them using
-the **Download codes** button for storage in a safe place. If you choose to download them, the file will be called **gitlab-recovery-codes.txt**.
-
CAUTION: **Caution:**
Each code can be used only once to log in to your account.
+Immediately after successfully enabling two-factor authentication, you'll be
+prompted to download a set of set recovery codes. Should you ever lose access
+to your one time password authenticator, you can use one of them to log in to
+your account. We suggest copying them, printing them, or downloading them using
+the **Download codes** button for storage in a safe place. If you choose to
+download them, the file will be called **gitlab-recovery-codes.txt**.
+
If you lose the recovery codes or just want to generate new ones, you can do so
[using SSH](#generate-new-recovery-codes-using-ssh).
@@ -99,24 +113,26 @@ Logging in with 2FA enabled is only slightly different than a normal login.
Enter your username and password credentials as you normally would, and you'll
be presented with a second prompt, depending on which type of 2FA you've enabled.
-### Log in via mobile application
-
-Enter the pin from your one time password authenticator's application or a recovery code to log in.
+### Log in via a one-time password
-![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png)
+When asked, enter the pin from your one time password authenticator's application or a
+recovery code to log in.
### Log in via U2F device
-1. Click **Login via U2F Device**.
-1. A light will start blinking on your device. Activate it by pressing its button.
+To log in via a U2F device:
-You will see a message indicating that your device responded to the authentication request.
-Click on **Authenticate via U2F Device** to complete the process.
+1. Click **Login via U2F Device**.
+1. A light will start blinking on your device. Activate it by touching/pressing
+ its button.
-![Two-Factor Authentication on sign in via U2F device](img/2fa_u2f_authenticate.png)
+You will see a message indicating that your device responded to the authentication
+request and you will be automatically logged in.
## Disabling 2FA
+If you ever need to disable 2FA:
+
1. Log in to your GitLab account.
1. Go to your **Profile Settings**.
1. Go to **Account**.
@@ -129,7 +145,8 @@ applications and U2F devices.
When 2FA is enabled, you can no longer use your normal account password to
authenticate with Git over HTTPS on the command line or when using
-[GitLab's API][api], you must use a [personal access token][pat] instead.
+[GitLab's API](../../../api/README.md). You must use a
+[personal access token](../personal_access_tokens.md) instead.
## Recovery options
@@ -148,7 +165,6 @@ codes. If you saved these codes, you can use one of them to sign in.
To use a recovery code, enter your username/email and password on the GitLab
sign-in page. When prompted for a two-factor code, enter the recovery code.
-> **Note:**
Once you use a recovery code, you cannot re-use it. You can still use the other
recovery codes you saved.
@@ -156,13 +172,18 @@ recovery codes you saved.
Users often forget to save their recovery codes when enabling two-factor
authentication. If an SSH key is added to your GitLab account, you can generate
-a new set of recovery codes with SSH.
+a new set of recovery codes with SSH:
-1. Run `ssh git@gitlab.example.com 2fa_recovery_codes`.
-1. You are prompted to confirm that you want to generate new codes. Continuing this process invalidates previously saved codes.
+1. Run:
+
+ ```sh
+ ssh git@gitlab.example.com 2fa_recovery_codes
+ ```
+
+1. You will then be prompted to confirm that you want to generate new codes.
+ Continuing this process invalidates previously saved codes:
```sh
- $ ssh git@gitlab.example.com 2fa_recovery_codes
Are you sure you want to generate new two-factor recovery codes?
Any existing recovery codes you saved will be invalidated. (yes/no)
@@ -190,7 +211,6 @@ a new set of recovery codes with SSH.
When prompted for a two-factor code, enter one of the recovery codes obtained
from the command-line output.
-> **Note:**
After signing in, visit your **Profile settings > Account** immediately to set
up two-factor authentication with a new device.
@@ -218,9 +238,3 @@ Sign in and re-enable two-factor authentication as soon as possible.
- The user logs out and attempts to log in via `first.host.xyz` - U2F authentication succeeds.
- The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
the U2F key has only been registered on `first.host.xyz`.
-
-[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
-[FreeOTP]: https://freeotp.github.io/
-[YubiKey]: https://www.yubico.com/products/yubikey-hardware/
-[api]: ../../../api/README.md
-[pat]: ../personal_access_tokens.md
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 97d2dfc0f7e..c6ee168bad0 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -533,22 +533,20 @@ This job failed because the necessary resources were not successfully created.
To find the cause of this error when creating a namespace and service account, check the [logs](../../../administration/logs.md#kuberneteslog).
-NOTE: **NOTE:**
-As of GitLab 12.1 we require [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
-tokens for all project level clusters unless you unselect the
-[GitLab-managed cluster](#gitlab-managed-clusters) option. If you
-want to manage namespaces and service accounts yourself and don't
-want to provide a `cluster-admin` token to GitLab you must unselect this
-option or you will get the above error.
-
-Common reasons for failure include:
+Reasons for failure include:
-- The token you gave GitLab did not have [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
+- The token you gave GitLab does 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.
+NOTE: **NOTE:**
+Project-level clusters upgraded from GitLab 12.0 or older may be configured
+in a way that causes this error. Ensure you deselect the
+[GitLab-managed cluster](#gitlab-managed-clusters) option if you want to manage
+namespaces and service accounts yourself.
+
## Monitoring your Kubernetes cluster **[ULTIMATE]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6.
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 91f0e24b44e..a06c3d3c662 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -94,10 +94,55 @@ adding an existing installation of Knative.
It is also possible to use GitLab Serverless with an existing Kubernetes
cluster which already has Knative installed.
-Simply:
+You must do the following:
1. Follow the steps to
[add an existing Kubernetes cluster](../index.md#adding-an-existing-kubernetes-cluster).
+
+1. Ensure GitLab can manage Knative:
+ - For a non-GitLab managed cluster, ensure that the service account for the token
+ provided can manage resources in the `serving.knative.dev` API group.
+ - For a GitLab managed cluster,
+ GitLab uses a service account with the `edit` cluster role. This account needs
+ the ability to manage resources in the `serving.knative.dev` API group.
+ We suggest you do this with an [aggregated ClusterRole](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#aggregated-clusterroles)
+ adding rules to the default `edit` cluster role:
+ First, save the following YAML as `knative-serving-only-role.yaml`:
+
+ ```yaml
+ apiVersion: rbac.authorization.k8s.io/v1
+ kind: ClusterRole
+ metadata:
+ name: knative-serving-only-role
+ labels:
+ rbac.authorization.k8s.io/aggregate-to-edit: "true"
+ rules:
+ - apiGroups:
+ - serving.knative.dev
+ resources:
+ - configurations
+ - configurationgenerations
+ - routes
+ - revisions
+ - revisionuids
+ - autoscalers
+ - services
+ verbs:
+ - get
+ - list
+ - create
+ - update
+ - delete
+ - patch
+ - watch
+ ```
+
+ Then run the following command:
+
+ ```bash
+ kubectl apply -f knative-serving-only-role.yaml
+ ```
+
1. Follow the steps to deploy [functions](#deploying-functions)
or [serverless applications](#deploying-serverless-applications) onto your
cluster.
diff --git a/doc/user/project/deploy_tokens/img/deploy_tokens.png b/doc/user/project/deploy_tokens/img/deploy_tokens.png
index 55c537fd1d3..421aa1ab3e5 100644
--- a/doc/user/project/deploy_tokens/img/deploy_tokens.png
+++ b/doc/user/project/deploy_tokens/img/deploy_tokens.png
Binary files differ
diff --git a/doc/user/project/deploy_tokens/index.md b/doc/user/project/deploy_tokens/index.md
index 92a29b68a22..5e11e7c0203 100644
--- a/doc/user/project/deploy_tokens/index.md
+++ b/doc/user/project/deploy_tokens/index.md
@@ -15,7 +15,7 @@ You can create as many deploy tokens as you like from the settings of your proje
1. Go to the project you want to create Deploy Tokens for.
1. Go to **Settings** > **Repository**.
1. Click on "Expand" on **Deploy Tokens** section.
-1. Choose a name and optionally an expiry date for the token.
+1. Choose a name, expiry date (optional), and username (optional) for the token.
1. Choose the [desired scopes](#limiting-scopes-of-a-deploy-token).
1. Click on **Create deploy token**.
1. Save the deploy token somewhere safe. Once you leave or refresh
@@ -39,6 +39,13 @@ the following table.
| `read_repository` | Allows read-access to the repository through `git clone` |
| `read_registry` | Allows read-access to [container registry] images if a project is private and authorization is required. |
+## Deploy token custom username
+
+> [Introduced][https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29639] in GitLab 12.1.
+
+The default username format is `gitlab+deploy-token-#{n}`. Some tools or platforms may not support this format,
+in such case you can specify custom username to be used when creating the deploy token.
+
## Usage
### Git clone a repository
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 587b4121e4e..06286951e20 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -193,6 +193,28 @@ password <personal_access_token>
To quickly access a project from the GitLab UI using the project ID,
visit the `/projects/:id` URL in your browser or other tool accessing the project.
+## Project aliases **[PREMIUM ONLY]**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3264) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.1.
+
+When migrating repositories to GitLab and they are being accessed by other systems,
+it's very useful to be able to access them using the same name especially when
+they are a lot. It reduces the risk of changing significant number of Git URLs in
+a large number of systems.
+
+GitLab provides a functionality to help with this. In GitLab, repositories are
+usually accessed with a namespace and project name. It is also possible to access
+them via a project alias. This feature is only available on Git over SSH.
+
+A project alias can be only created via API and only by GitLab administrators.
+Follow the [Project Aliases API documentation](../../api/project_aliases.md) for
+more details.
+
+Once an alias has been created for a project (e.g., an alias `gitlab-ce` for the
+project `https://gitlab.com/gitlab-org/gitlab-ce`), the repository can be cloned
+using the alias (e.g `git clone git@gitlab.com:gitlab-ce.git` instead of
+`git clone git@gitlab.com:gitlab-org/gitlab-ce.git`).
+
## Project APIs
There are numerous [APIs](../../api/README.md) to use with your projects:
@@ -212,3 +234,4 @@ There are numerous [APIs](../../api/README.md) to use with your projects:
- [Templates](../../api/project_templates.md)
- [Traffic](../../api/project_statistics.md)
- [Variables](../../api/project_level_variables.md)
+- [Aliases](../../api/project_aliases.md)
diff --git a/doc/user/project/insights/index.md b/doc/user/project/insights/index.md
index 2e2a27f112e..1c6ad0b8b2b 100644
--- a/doc/user/project/insights/index.md
+++ b/doc/user/project/insights/index.md
@@ -1,7 +1,6 @@
# Insights **[ULTIMATE]**
-> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.9 behind the `insights` feature flag.
-> **Generally Available** (GA) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/725) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
Configure the Insights that matter for your projects to explore data such as
triage hygiene, issues created/closed per a given period, average time for merge
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 234e3ad31cc..8f2e5a55b5f 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -39,7 +39,7 @@ a GitLab project with any single Jira project.
If you have one Jira instance, you can pre-fill the settings page with a default
template. See the [Services Templates][services-templates] docs.
-Configuration happens via user name and password. Connecting to a Jira server
+Configuration happens via user name and password. Connecting to a Jira Server
via CAS is not possible.
In order to enable the Jira service in GitLab, you need to first configure the
@@ -47,13 +47,13 @@ project in Jira and then enter the correct values in GitLab.
### Configuring Jira
-When connecting to **JIRA Server**, which supports basic authentication, a **username and password** are required. Check the link below and proceed to the next step:
+When connecting to **Jira Server**, which supports basic authentication, a **username and password** are required. Check the link below and proceed to the next step:
-- [Setting up a user in JIRA server](jira_server_configuration.md)
+- [Setting up a user in Jira Server](jira_server_configuration.md)
-When connecting to **JIRA Cloud**, which supports authentication via API token, an **email and API token**, are required. Check the link below and proceed to the next step:
+When connecting to **Jira Cloud**, which supports authentication via API token, an **email and API token**, are required. Check the link below and proceed to the next step:
-- [Setting up a user in JIRA cloud](jira_cloud_configuration.md)
+- [Setting up a user in Jira Cloud](jira_cloud_configuration.md)
### Configuring GitLab
@@ -77,8 +77,8 @@ in the table below.
| ----- | ----------- |
| `Web URL` | The base URL to the Jira instance web interface which is being linked to this GitLab project. E.g., `https://Jira.example.com`. |
| `Jira API URL` | The base URL to the Jira instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. |
-| `Username/Email` | Created when [configuring Jira step](#configuring-jira). Use `username` for **JIRA server** or `email` for **JIRA cloud**. |
-| `Password/API token` |Created in [configuring Jira step](#configuring-jira). Use `password` for **JIRA server** or `API token` for **JIRA cloud**. |
+| `Username/Email` | Created when [configuring Jira step](#configuring-jira). Use `username` for **Jira Server** or `email` for **Jira Cloud**. |
+| `Password/API token` |Created in [configuring Jira step](#configuring-jira). Use `password` for **Jira Server** or `API token` for **Jira Cloud**. |
| `Transition ID` | This is the ID of a transition that moves issues to the desired state. It is possible to insert transition ids separated by `,` or `;` which means the issue will be moved to each state after another using the given order. **Closing Jira issues via commits or Merge Requests won't work if you don't set the ID correctly.** |
### Obtaining a transition ID
diff --git a/doc/user/project/integrations/jira_cloud_configuration.md b/doc/user/project/integrations/jira_cloud_configuration.md
index 849df707521..614f05d5b7e 100644
--- a/doc/user/project/integrations/jira_cloud_configuration.md
+++ b/doc/user/project/integrations/jira_cloud_configuration.md
@@ -1,18 +1,18 @@
-# Creating an API token in JIRA cloud
+# Creating an API token in Jira Cloud
-An API token is needed when integrating with JIRA Cloud, follow the steps
+An API token is needed when integrating with Jira Cloud, follow the steps
below to create one:
1. Log in to <https://id.atlassian.com> with your email.
1. **Click API tokens**, then **Create API token**.
-![JIRA API token](img/jira_api_token_menu.png)
+![Jira API token](img/jira_api_token_menu.png)
-![JIRA API token](img/jira_api_token.png)
+![Jira API token](img/jira_api_token.png)
1. Make sure to write down your new API token as you will need it in the next [steps](jira.md#configuring-gitlab).
NOTE: **Note**
-It is important that the user associated with this email has 'write' access to projects in JIRA.
+It is important that the user associated with this email has 'write' access to projects in Jira.
-The JIRA configuration is complete. You are going to need this new created token and the email you used to log in when [configuring GitLab in the next section](jira.md#configuring-gitlab).
+The Jira configuration is complete. You are going to need this newly created token and the email you used to log in, when [configuring GitLab in the next section](jira.md#configuring-gitlab).
diff --git a/doc/user/project/integrations/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index 13d65c4d8e4..32991714973 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -1,4 +1,4 @@
-# Creating a username and password for JIRA server
+# Creating a username and password for Jira Server
We need to create a user in Jira which will have access to all projects that
need to integrate with GitLab.
diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md
index 0bfee3bac99..0e4c71a9d3e 100644
--- a/doc/user/project/integrations/project_services.md
+++ b/doc/user/project/integrations/project_services.md
@@ -38,7 +38,7 @@ Click on the service links to see further configuration instructions and details
| [Hangouts Chat](hangouts_chat.md) | Receive events notifications in Google Hangouts Chat |
| [HipChat](hipchat.md) | Private group chat and IM |
| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
-| [JIRA](jira.md) | JIRA issue tracker |
+| [Jira](jira.md) | Jira issue tracker |
| [Jenkins](../../../integration/jenkins.md) **[STARTER]** | An extendable open source continuous integration server |
| JetBrains TeamCity CI | A continuous integration and build server |
| [Mattermost slash commands](mattermost_slash_commands.md) | Mattermost chat and ChatOps slash commands |
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 6c430ff7cd9..1281ba561b8 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -57,6 +57,7 @@ discussions, and descriptions:
| `/approve` | Approve the merge request | | ✓ |
| `/merge` | Merge (when pipeline succeeds) | | ✓ |
| `/create_merge_request <branch name>` | Create a new merge request starting from the current issue | ✓ | |
+| `/relate #issue1 #issue2` | Mark issues as related **[STARTER]** | ✓ | |
## Quick actions for commit messages
diff --git a/lib/api/boards.rb b/lib/api/boards.rb
index b7c77730afb..4e31f74f18a 100644
--- a/lib/api/boards.rb
+++ b/lib/api/boards.rb
@@ -27,7 +27,7 @@ module API
end
get '/' do
authorize!(:read_board, user_project)
- present paginate(board_parent.boards), with: Entities::Board
+ present paginate(board_parent.boards.with_associations), with: Entities::Board
end
desc 'Find a project board' do
diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb
index 86d9b24802f..68497a08fb8 100644
--- a/lib/api/boards_responses.rb
+++ b/lib/api/boards_responses.rb
@@ -11,7 +11,7 @@ module API
end
def board_lists
- board.lists.destroyable
+ board.destroyable_lists
end
def create_list
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index ead01dc53f7..d783591c238 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1101,7 +1101,7 @@ module API
expose :project, using: Entities::BasicProjectDetails
expose :lists, using: Entities::List do |board|
- board.lists.destroyable
+ board.destroyable_lists
end
end
diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb
index 9a20ee8c8b9..feb2254963e 100644
--- a/lib/api/group_boards.rb
+++ b/lib/api/group_boards.rb
@@ -37,7 +37,7 @@ module API
use :pagination
end
get '/' do
- present paginate(board_parent.boards), with: Entities::Board
+ present paginate(board_parent.boards.with_associations), with: Entities::Board
end
end
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index cf2e9d01356..c4ecf55969c 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -462,31 +462,31 @@ module API
required: true,
name: :url,
type: String,
- desc: 'The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
+ desc: 'The base URL to the Jira instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
},
{
required: false,
name: :api_url,
type: String,
- desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
+ desc: 'The base URL to the Jira instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
},
{
required: true,
name: :username,
type: String,
- desc: 'The username of the user created to be used with GitLab/JIRA'
+ desc: 'The username of the user created to be used with GitLab/Jira'
},
{
required: true,
name: :password,
type: String,
- desc: 'The password of the user created to be used with GitLab/JIRA'
+ desc: 'The password of the user created to be used with GitLab/Jira'
},
{
required: false,
name: :jira_issue_transition_id,
type: String,
- desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
+ desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the Jira workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
}
],
'kubernetes' => [
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 80c84c0f622..88836e12e61 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -56,10 +56,10 @@ module Banzai
def process_link_to_upload_attr(html_attr)
path_parts = [Addressable::URI.unescape(html_attr.value)]
- if group
- path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
- elsif project
+ if project
path_parts.unshift(relative_url_root, project.full_path)
+ elsif group
+ path_parts.unshift(relative_url_root, 'groups', group.full_path, '-')
else
path_parts.unshift(relative_url_root)
end
diff --git a/lib/gitlab/auth/ip_rate_limiter.rb b/lib/gitlab/auth/ip_rate_limiter.rb
index 81e616fa20a..0b7055b3256 100644
--- a/lib/gitlab/auth/ip_rate_limiter.rb
+++ b/lib/gitlab/auth/ip_rate_limiter.rb
@@ -3,6 +3,8 @@
module Gitlab
module Auth
class IpRateLimiter
+ include ::Gitlab::Utils::StrongMemoize
+
attr_reader :ip
def initialize(ip)
@@ -37,7 +39,20 @@ module Gitlab
end
def ip_can_be_banned?
- config.ip_whitelist.exclude?(ip)
+ !trusted_ip?
+ end
+
+ def trusted_ip?
+ trusted_ips.any? { |netmask| netmask.include?(ip) }
+ end
+
+ def trusted_ips
+ strong_memoize(:trusted_ips) do
+ config.ip_whitelist.map do |proxy|
+ IPAddr.new(proxy)
+ rescue IPAddr::InvalidAddressError
+ end.compact
+ end
end
end
end
diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
index 49c680605ea..e6e0aaab60b 100644
--- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -20,7 +20,7 @@ module Gitlab
private
def deployment_cluster
- build.deployment&.deployment_platform_cluster
+ build.deployment&.cluster
end
def kubernetes_namespace
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index dfae260239e..ce5857965bf 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -5,7 +5,7 @@ module Gitlab
class Trace
include ::Gitlab::ExclusiveLeaseHelpers
- LOCK_TTL = 1.minute
+ LOCK_TTL = 10.minutes
LOCK_RETRIES = 2
LOCK_SLEEP = 0.001.seconds
diff --git a/lib/gitlab/cleanup/orphan_job_artifact_files.rb b/lib/gitlab/cleanup/orphan_job_artifact_files.rb
new file mode 100644
index 00000000000..ee7164b3e55
--- /dev/null
+++ b/lib/gitlab/cleanup/orphan_job_artifact_files.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Cleanup
+ class OrphanJobArtifactFiles
+ include Gitlab::Utils::StrongMemoize
+
+ ABSOLUTE_ARTIFACT_DIR = ::JobArtifactUploader.root.freeze
+ LOST_AND_FOUND = File.join(ABSOLUTE_ARTIFACT_DIR, '-', 'lost+found').freeze
+ BATCH_SIZE = 500
+ DEFAULT_NICENESS = 'Best-effort'
+
+ attr_accessor :batch, :total_found, :total_cleaned
+ attr_reader :limit, :dry_run, :niceness, :logger
+
+ def initialize(limit: nil, dry_run: true, niceness: nil, logger: nil)
+ @limit = limit
+ @dry_run = dry_run
+ @niceness = niceness || DEFAULT_NICENESS
+ @logger = logger || Rails.logger
+ @total_found = @total_cleaned = 0
+
+ new_batch!
+ end
+
+ def run!
+ log_info('Looking for orphan job artifacts to clean up')
+
+ find_artifacts do |artifact_file|
+ batch << artifact_file
+
+ clean_batch! if batch.full?
+ break if limit_reached?
+ end
+
+ clean_batch!
+
+ log_info("Processed #{total_found} job artifacts to find and clean #{total_cleaned} orphans.")
+ end
+
+ private
+
+ def new_batch!
+ self.batch = ::Gitlab::Cleanup::OrphanJobArtifactFilesBatch
+ .new(batch_size: batch_size, logger: logger, dry_run: dry_run)
+ end
+
+ def clean_batch!
+ batch.clean!
+
+ update_stats!(batch)
+
+ new_batch!
+ end
+
+ def update_stats!(batch)
+ self.total_found += batch.artifact_files.count
+ self.total_cleaned += batch.lost_and_found.count
+ end
+
+ def limit_reached?
+ return false unless limit
+
+ total_cleaned >= limit
+ end
+
+ def batch_size
+ return BATCH_SIZE unless limit
+ return if limit_reached?
+
+ todo = limit - total_cleaned
+ [BATCH_SIZE, todo].min
+ end
+
+ def find_artifacts
+ Open3.popen3(*find_command) do |stdin, stdout, stderr, status_thread|
+ stdout.each_line do |line|
+ yield line
+ end
+
+ log_error(stderr.read.color(:red)) unless status_thread.value.success?
+ end
+ end
+
+ def find_command
+ strong_memoize(:find_command) do
+ cmd = %W[find -L #{absolute_artifact_dir}]
+
+ # Search for Job Artifact IDs, they are found 6 directory
+ # levels deep. For example:
+ # shared/artifacts/2c/62/2c...a3/2019_02_27/836/628/job.log
+ # 1 2 3 4 5 6
+ # | | | ^- date | ^- Job Artifact ID
+ # | | | ^- Job ID
+ # ^--+--+- components of hashed storage project path
+ cmd += %w[-mindepth 6 -maxdepth 6]
+
+ # Artifact directories are named on their ID
+ cmd += %w[-type d]
+
+ if ionice
+ raise ArgumentError, 'Invalid niceness' unless niceness.match?(/^\w[\w\-]*$/)
+
+ cmd.unshift(*%W[#{ionice} --class #{niceness}])
+ end
+
+ log_info("find command: '#{cmd.join(' ')}'")
+
+ cmd
+ end
+ end
+
+ def absolute_artifact_dir
+ File.absolute_path(ABSOLUTE_ARTIFACT_DIR)
+ end
+
+ def ionice
+ strong_memoize(:ionice) do
+ Gitlab::Utils.which('ionice')
+ end
+ end
+
+ def log_info(msg, params = {})
+ logger.info("#{'[DRY RUN]' if dry_run} #{msg}")
+ end
+
+ def log_error(msg, params = {})
+ logger.error(msg)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb b/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb
new file mode 100644
index 00000000000..5c30258c0fc
--- /dev/null
+++ b/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Cleanup
+ class OrphanJobArtifactFilesBatch
+ BatchFull = Class.new(StandardError)
+
+ class ArtifactFile
+ attr_accessor :path
+
+ def initialize(path)
+ @path = path
+ end
+
+ def artifact_id
+ path.split('/').last.to_i
+ end
+ end
+
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :batch_size, :dry_run
+ attr_accessor :artifact_files
+
+ def initialize(batch_size:, dry_run: true, logger: Rails.logger)
+ @batch_size = batch_size
+ @dry_run = dry_run
+ @logger = logger
+ @artifact_files = []
+ end
+
+ def clean!
+ return if artifact_files.empty?
+
+ lost_and_found.each do |artifact|
+ clean_one!(artifact)
+ end
+ end
+
+ def full?
+ artifact_files.count >= batch_size
+ end
+
+ def <<(artifact_path)
+ raise BatchFull, "Batch full! Already contains #{artifact_files.count} artifacts" if full?
+
+ artifact_files << ArtifactFile.new(artifact_path)
+ end
+
+ def lost_and_found
+ strong_memoize(:lost_and_found) do
+ artifact_file_ids = artifact_files.map(&:artifact_id)
+ existing_artifact_ids = ::Ci::JobArtifact.id_in(artifact_file_ids).pluck_primary_key
+
+ artifact_files.reject { |artifact| existing_artifact_ids.include?(artifact.artifact_id) }
+ end
+ end
+
+ private
+
+ def clean_one!(artifact_file)
+ log_debug("Found orphan job artifact file @ #{artifact_file.path}")
+
+ remove_file!(artifact_file) unless dry_run
+ end
+
+ def remove_file!(artifact_file)
+ FileUtils.rm_rf(artifact_file.path)
+ end
+
+ def log_info(msg, params = {})
+ @logger.info("#{'[DRY RUN]' if dry_run} #{msg}")
+ end
+
+ def log_debug(msg, params = {})
+ @logger.debug(msg)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cluster/lifecycle_events.rb b/lib/gitlab/cluster/lifecycle_events.rb
index e0f9eb59924..8f796748199 100644
--- a/lib/gitlab/cluster/lifecycle_events.rb
+++ b/lib/gitlab/cluster/lifecycle_events.rb
@@ -11,6 +11,9 @@ module Gitlab
# We have three lifecycle events.
#
# - before_fork (only in forking processes)
+ # In forking processes (Unicorn and Puma in multiprocess mode) this
+ # will be called exactly once, on startup, before the workers are
+ # forked. This will be called in the parent process.
# - worker_start
# - before_master_restart (only in forking processes)
#
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index e4d4779ba9a..34c1e6ad8ca 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -2,6 +2,8 @@
module Gitlab
module Database
+ include Gitlab::Metrics::Methods
+
# The max value of INTEGER type is the same between MySQL and PostgreSQL:
# https://www.postgresql.org/docs/9.2/static/datatype-numeric.html
# http://dev.mysql.com/doc/refman/5.7/en/integer-types.html
@@ -11,6 +13,15 @@ module Gitlab
# https://dev.mysql.com/doc/refman/5.7/en/datetime.html
MAX_TIMESTAMP_VALUE = Time.at((1 << 31) - 1).freeze
+ # Minimum schema version from which migrations are supported
+ # Migrations before this version may have been removed
+ MIN_SCHEMA_VERSION = 20190506135400
+ MIN_SCHEMA_GITLAB_VERSION = '11.11.0'
+
+ define_histogram :gitlab_database_transaction_seconds do
+ docstring "Time spent in database transactions, in seconds"
+ end
+
def self.config
ActiveRecord::Base.configurations[Rails.env]
end
@@ -286,5 +297,32 @@ module Gitlab
0
end
private_class_method :open_transactions_baseline
+
+ # Monkeypatch rails with upgraded database observability
+ def self.install_monkey_patches
+ ActiveRecord::Base.prepend(ActiveRecordBaseTransactionMetrics)
+ end
+
+ # observe_transaction_duration is called from ActiveRecordBaseTransactionMetrics.transaction and used to
+ # record transaction durations.
+ def self.observe_transaction_duration(duration_seconds)
+ labels = Gitlab::Metrics::Transaction.current&.labels || {}
+ gitlab_database_transaction_seconds.observe(labels, duration_seconds)
+ rescue Prometheus::Client::LabelSetValidator::LabelSetError => err
+ # Ensure that errors in recording these metrics don't affect the operation of the application
+ Rails.logger.error("Unable to observe database transaction duration: #{err}")
+ end
+
+ # MonkeyPatch for ActiveRecord::Base for adding observability
+ module ActiveRecordBaseTransactionMetrics
+ # A monkeypatch over ActiveRecord::Base.transaction.
+ # It provides observability into transactional methods.
+ def transaction(options = {}, &block)
+ start_time = Gitlab::Metrics::System.monotonic_time
+ super(options, &block)
+ ensure
+ Gitlab::Database.observe_transaction_duration(Gitlab::Metrics::System.monotonic_time - start_time)
+ end
+ end
end
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 0b12e862ded..e2cbf91f281 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -434,7 +434,8 @@ module Gitlab
end
begin
- update_column_in_batches(table, column, default, &block)
+ default_after_type_cast = connection.type_cast(default, column_for(table, column))
+ update_column_in_batches(table, column, default_after_type_cast, &block)
change_column_null(table, column, false) unless allow_null
# We want to rescue _all_ exceptions here, even those that don't inherit
diff --git a/lib/gitlab/graphql/copy_field_description.rb b/lib/gitlab/graphql/copy_field_description.rb
new file mode 100644
index 00000000000..edd73083ff2
--- /dev/null
+++ b/lib/gitlab/graphql/copy_field_description.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module CopyFieldDescription
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Returns the `description` for property of field `field_name` on type.
+ # This can be used to ensure, for example, that mutation argument descriptions
+ # are always identical to the corresponding query field descriptions.
+ #
+ # E.g.:
+ # argument :name, GraphQL::STRING_TYPE, description: copy_field_description(Types::UserType, :name)
+ def copy_field_description(type, field_name)
+ type.fields[field_name.to_s.camelize(:lower)].description
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/errors.rb b/lib/gitlab/graphql/errors.rb
index fe74549e322..40b90310e8b 100644
--- a/lib/gitlab/graphql/errors.rb
+++ b/lib/gitlab/graphql/errors.rb
@@ -6,6 +6,7 @@ module Gitlab
BaseError = Class.new(GraphQL::ExecutionError)
ArgumentError = Class.new(BaseError)
ResourceNotAvailable = Class.new(BaseError)
+ MutationError = Class.new(BaseError)
end
end
end
diff --git a/lib/gitlab/graphql/find_argument_in_parent.rb b/lib/gitlab/graphql/find_argument_in_parent.rb
new file mode 100644
index 00000000000..1f83f8fce7a
--- /dev/null
+++ b/lib/gitlab/graphql/find_argument_in_parent.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module FindArgumentInParent
+ # Searches up the GraphQL AST and returns the first matching argument
+ # passed to a node
+ def self.find(parent, argument, limit_depth: nil)
+ argument = argument.to_s.camelize(:lower).to_sym
+ depth = 0
+
+ while parent.respond_to?(:parent)
+ args = node_args(parent)
+ return args[argument] if args.key?(argument)
+
+ depth += 1
+ return if limit_depth && depth >= limit_depth
+
+ parent = parent.parent
+ end
+ end
+
+ class << self
+ private
+
+ def node_args(node)
+ node.irep_node.arguments
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb b/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb
new file mode 100644
index 00000000000..81c5cabf451
--- /dev/null
+++ b/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Loaders
+ class PipelineForShaLoader
+ attr_accessor :project, :sha
+
+ def initialize(project, sha)
+ @project, @sha = project, sha
+ end
+
+ def find_last
+ BatchLoader.for(sha).batch(key: project) do |shas, loader, args|
+ pipelines = args[:key].ci_pipelines.latest_for_shas(shas)
+
+ pipelines.each do |pipeline|
+ loader.call(pipeline.sha, pipeline)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 33c0de91c11..34de40ca72f 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -57,17 +57,9 @@ module Gitlab
end
end
- # THREAD_CPUTIME is not supported on OS X
- if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
- def self.cpu_time
- Process
- .clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second)
- end
- else
- def self.cpu_time
- Process
- .clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :float_second)
- end
+ def self.cpu_time
+ Process
+ .clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :float_second)
end
# Returns the current real time in a given precision.
diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb
index 868b2ae641a..0c0f46d3b77 100644
--- a/lib/gitlab/optimistic_locking.rb
+++ b/lib/gitlab/optimistic_locking.rb
@@ -5,6 +5,7 @@ module Gitlab
module_function
def retry_lock(subject, retries = 100, &block)
+ # TODO(Observability): We should be recording details of the number of retries and the duration of the total execution here
ActiveRecord::Base.transaction do
yield(subject)
end
diff --git a/lib/gitlab/search/found_blob.rb b/lib/gitlab/search/found_blob.rb
index 01ce90c85f7..fa09ecbdf30 100644
--- a/lib/gitlab/search/found_blob.rb
+++ b/lib/gitlab/search/found_blob.rb
@@ -28,7 +28,7 @@ module Gitlab
@binary_data = opts.fetch(:data, nil)
@per_page = opts.fetch(:per_page, 20)
@project = opts.fetch(:project, nil)
- # Some caller does not have project object (e.g. elastic search),
+ # Some callers (e.g. Elasticsearch) do not have the Project object,
# yet they can trigger many calls in one go,
# causing duplicated queries.
# Allow those to just pass project_id instead.
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 760331620ef..105ef417df3 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -115,6 +115,18 @@ namespace :gitlab do
end
end
+ desc 'GitLab | Cleanup | Clean orphan job artifact files'
+ task orphan_job_artifact_files: :gitlab_environment do
+ warn_user_is_not_gitlab
+
+ cleaner = Gitlab::Cleanup::OrphanJobArtifactFiles.new(limit: limit, dry_run: dry_run?, niceness: niceness, logger: logger)
+ cleaner.run!
+
+ if dry_run?
+ logger.info "To clean up these files run this command with DRY_RUN=false".color(:yellow)
+ end
+ end
+
def remove?
ENV['REMOVE'] == 'true'
end
@@ -123,12 +135,25 @@ namespace :gitlab do
ENV['DRY_RUN'] != 'false'
end
+ def debug?
+ ENV['DEBUG'].present?
+ end
+
+ def limit
+ ENV['LIMIT']&.to_i
+ end
+
+ def niceness
+ ENV['NICENESS'].presence
+ end
+
def logger
return @logger if defined?(@logger)
@logger = if Rails.env.development? || Rails.env.production?
Logger.new(STDOUT).tap do |stdout_logger|
stdout_logger.extend(ActiveSupport::Logger.broadcast(Rails.logger))
+ stdout_logger.level = debug? ? Logger::DEBUG : Logger::INFO
end
else
Rails.logger
diff --git a/lib/tasks/migrate/schema_check.rake b/lib/tasks/migrate/schema_check.rake
new file mode 100644
index 00000000000..76f1f23c7bd
--- /dev/null
+++ b/lib/tasks/migrate/schema_check.rake
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# Configures the database by running migrate, or by loading the schema and seeding if needed
+task schema_version_check: :environment do
+ next if ENV['SKIP_SCHEMA_VERSION_CHECK']
+
+ schema_version = ActiveRecord::Migrator.current_version
+
+ # Ensure migrations are being run from a supported schema version
+ # A schema verison of 0 is a fresh db, and should be safe to run migrations
+ # But a database with existing migrations less than our min version is not
+ if schema_version > 0 && schema_version < Gitlab::Database::MIN_SCHEMA_VERSION
+ raise "Your current database version is too old to be migrated. " \
+ "You should upgrade to GitLab #{Gitlab::Database::MIN_SCHEMA_GITLAB_VERSION} before moving to this version. " \
+ "Please see https://docs.gitlab.com/ee/policy/maintenance.html#upgrade-recommendations"
+ end
+end
+
+# Ensure the check is a pre-requisite when running db:migrate
+Rake::Task["db:migrate"].enhance [:schema_version_check]
diff --git a/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po
index 1b561621f6f..7ccdfa282e8 100644
--- a/locale/ar_SA/gitlab.po
+++ b/locale/ar_SA/gitlab.po
@@ -7581,13 +7581,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 4d72599f5a3..9e24b950cdd 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/bn_BD/gitlab.po b/locale/bn_BD/gitlab.po
index c1b139055cc..c7c0695b580 100644
--- a/locale/bn_BD/gitlab.po
+++ b/locale/bn_BD/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/bn_IN/gitlab.po b/locale/bn_IN/gitlab.po
index 8c3512497e7..56c5a3b3704 100644
--- a/locale/bn_IN/gitlab.po
+++ b/locale/bn_IN/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po
index 69c1971701f..c0b4aea4a99 100644
--- a/locale/ca_ES/gitlab.po
+++ b/locale/ca_ES/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po
index 2e072b2a16e..1184a326ede 100644
--- a/locale/cs_CZ/gitlab.po
+++ b/locale/cs_CZ/gitlab.po
@@ -7477,13 +7477,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/cy_GB/gitlab.po b/locale/cy_GB/gitlab.po
index 24ab2e96c78..019dcd25e72 100644
--- a/locale/cy_GB/gitlab.po
+++ b/locale/cy_GB/gitlab.po
@@ -7581,13 +7581,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po
index bada56b442a..f30fbc0806c 100644
--- a/locale/da_DK/gitlab.po
+++ b/locale/da_DK/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index daf877d3cff..7faeed34e59 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/el_GR/gitlab.po b/locale/el_GR/gitlab.po
index f2d8321020c..2ac09933c10 100644
--- a/locale/el_GR/gitlab.po
+++ b/locale/el_GR/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index 7960be28abd..df88e81f3d3 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index ca2c3450b4d..616ef854a7d 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr "Los eventos para %{noteable_model_name} están deshabilitados."
msgid "JiraService|If different from Web URL"
msgstr "Si es diferente de la URL Web"
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr "URL del API de JIRA"
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr "Los comentarios de JIRA se crearán cuando se haga referencia a una incidencia en un commit."
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr "Los comentarios de JIRA se crearán cuando se haga referencia a una incidencia en un commit."
msgid "JiraService|Jira issue tracker"
diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po
index f1ed6ff3630..c7473191a42 100644
--- a/locale/et_EE/gitlab.po
+++ b/locale/et_EE/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po
index 30b0e53ea19..be757241241 100644
--- a/locale/fil_PH/gitlab.po
+++ b/locale/fil_PH/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 12edcf96cff..9b938ee38e6 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9ea368816f9..c57a974fe11 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -251,6 +251,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
+msgid "%{username}'s avatar"
+msgstr ""
+
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
@@ -579,6 +582,11 @@ msgstr ""
msgid "Activity"
msgstr ""
+msgid "Add %d issue"
+msgid_plural "Add %d issues"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "Add CHANGELOG"
msgstr ""
@@ -639,6 +647,9 @@ msgstr ""
msgid "Add image comment"
msgstr ""
+msgid "Add issues"
+msgstr ""
+
msgid "Add italic text"
msgstr ""
@@ -1071,6 +1082,9 @@ msgstr ""
msgid "An error occurred. Please try again."
msgstr ""
+msgid "An issue can be a bug, a todo or a feature request that needs to be discussed in a project. Besides, issues are searchable and filterable."
+msgstr ""
+
msgid "Anonymous"
msgstr ""
@@ -1580,9 +1594,27 @@ msgstr ""
msgid "Blog"
msgstr ""
+msgid "BoardBlankState|Add default lists"
+msgstr ""
+
+msgid "BoardBlankState|Add the following default lists to your Issue Board with one click:"
+msgstr ""
+
+msgid "BoardBlankState|Nevermind, I'll use my own"
+msgstr ""
+
+msgid "BoardBlankState|Starting out with the default set of lists will get you right on the way to making the most of your board."
+msgstr ""
+
msgid "Boards"
msgstr ""
+msgid "Boards|Collapse"
+msgstr ""
+
+msgid "Boards|Expand"
+msgstr ""
+
msgid "Branch %{branchName} was not found in this project's repository."
msgstr ""
@@ -1841,6 +1873,9 @@ msgstr ""
msgid "Cancel"
msgstr ""
+msgid "Cancel running"
+msgstr ""
+
msgid "Cancel this job"
msgstr ""
@@ -1991,9 +2026,6 @@ msgstr ""
msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose your merge method, options, checks, and set up a default merge request description template."
-msgstr ""
-
msgid "CiStatusLabel|canceled"
msgstr ""
@@ -2123,6 +2155,9 @@ msgstr ""
msgid "Clear"
msgstr ""
+msgid "Clear recent searches"
+msgstr ""
+
msgid "Clear search"
msgstr ""
@@ -2237,6 +2272,9 @@ msgstr ""
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr ""
+msgid "ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored."
+msgstr ""
+
msgid "ClusterIntegration|All data will be deleted and cannot be restored."
msgstr ""
@@ -3447,6 +3485,9 @@ msgstr ""
msgid "Deploy key was successfully updated."
msgstr ""
+msgid "Deploy to..."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -3585,6 +3626,9 @@ msgstr ""
msgid "Description:"
msgstr ""
+msgid "Deselect all"
+msgstr ""
+
msgid "Destroy"
msgstr ""
@@ -3687,6 +3731,9 @@ msgstr ""
msgid "Dockerfile"
msgstr ""
+msgid "Doing"
+msgstr ""
+
msgid "Domain"
msgstr ""
@@ -3885,6 +3932,9 @@ msgstr ""
msgid "Enable error tracking"
msgstr ""
+msgid "Enable feature to choose access level"
+msgstr ""
+
msgid "Enable for this project"
msgstr ""
@@ -4101,6 +4151,9 @@ msgstr ""
msgid "Error fetching contributors data."
msgstr ""
+msgid "Error fetching diverging counts for branches. Please try again."
+msgstr ""
+
msgid "Error fetching labels."
msgstr ""
@@ -4272,6 +4325,9 @@ msgstr ""
msgid "Everyone"
msgstr ""
+msgid "Everyone With Access"
+msgstr ""
+
msgid "Everyone can contribute"
msgstr ""
@@ -4832,6 +4888,9 @@ msgstr ""
msgid "Go back"
msgstr ""
+msgid "Go back to %{startTag}Open issues%{endTag} and select some issues to add to your board."
+msgstr ""
+
msgid "Go full screen"
msgstr ""
@@ -5518,6 +5577,9 @@ msgstr ""
msgid "Invite member"
msgstr ""
+msgid "Invocations"
+msgstr ""
+
msgid "Invoke Count"
msgstr ""
@@ -5578,13 +5640,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
@@ -5975,6 +6037,9 @@ msgstr ""
msgid "Loading functions timed out. Please reload the page to try again."
msgstr ""
+msgid "Loading issues"
+msgstr ""
+
msgid "Loading the GitLab IDE..."
msgstr ""
@@ -6652,6 +6717,9 @@ msgstr ""
msgid "No"
msgstr ""
+msgid "No %{header} for this request."
+msgstr ""
+
msgid "No %{providerTitle} repositories available to import"
msgstr ""
@@ -6936,6 +7004,9 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
+msgid "Only Project Members"
+msgstr ""
+
msgid "Only admins"
msgstr ""
@@ -6969,6 +7040,9 @@ msgstr ""
msgid "Open in Xcode"
msgstr ""
+msgid "Open issues"
+msgstr ""
+
msgid "Open raw"
msgstr ""
@@ -7137,6 +7211,18 @@ msgstr ""
msgid "Performance optimization"
msgstr ""
+msgid "PerformanceBar|Gitaly calls"
+msgstr ""
+
+msgid "PerformanceBar|SQL queries"
+msgstr ""
+
+msgid "PerformanceBar|profile"
+msgstr ""
+
+msgid "PerformanceBar|trace"
+msgstr ""
+
msgid "Permissions"
msgstr ""
@@ -8058,6 +8144,9 @@ msgstr ""
msgid "ProjectSettings|Badges"
msgstr ""
+msgid "ProjectSettings|Choose your merge method, merge options, and merge checks."
+msgstr ""
+
msgid "ProjectSettings|Customize your project badges."
msgstr ""
@@ -8510,6 +8599,9 @@ msgstr ""
msgid "Remove fork relationship"
msgstr ""
+msgid "Remove from board"
+msgstr ""
+
msgid "Remove group"
msgstr ""
@@ -8887,6 +8979,9 @@ msgstr ""
msgid "Save variables"
msgstr ""
+msgid "Saving"
+msgstr ""
+
msgid "Saving project."
msgstr ""
@@ -9034,9 +9129,15 @@ msgstr ""
msgid "Select a new namespace"
msgstr ""
+msgid "Select a project"
+msgstr ""
+
msgid "Select a timezone"
msgstr ""
+msgid "Select all"
+msgstr ""
+
msgid "Select an existing Kubernetes cluster or create a new one"
msgstr ""
@@ -9124,6 +9225,9 @@ msgstr ""
msgid "ServerlessDetails|More information"
msgstr ""
+msgid "ServerlessDetails|No pods loaded at this time."
+msgstr ""
+
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
msgstr ""
@@ -9154,9 +9258,21 @@ msgstr ""
msgid "Serverless|No functions available"
msgstr ""
+msgid "Serverless|The deploy job has not finished."
+msgstr ""
+
+msgid "Serverless|The functions listed in the %{startTag}serverless.yml%{endTag} file don't match the namespace of your cluster."
+msgstr ""
+
msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
msgstr ""
+msgid "Serverless|Your %{startTag}.gitlab-ci.yml%{endTag} file is not properly configured."
+msgstr ""
+
+msgid "Serverless|Your repository does not have a corresponding %{startTag}serverless.yml%{endTag} file."
+msgstr ""
+
msgid "Service"
msgstr ""
@@ -9324,6 +9440,9 @@ msgid_plural "Showing %d events"
msgstr[0] ""
msgstr[1] ""
+msgid "Showing all issues"
+msgstr ""
+
msgid "Showing last %{size} of log -"
msgstr ""
@@ -9774,6 +9893,9 @@ msgstr ""
msgid "Submit feedback"
msgstr ""
+msgid "Submit issue"
+msgstr ""
+
msgid "Submit search"
msgstr ""
@@ -10047,6 +10169,9 @@ msgstr ""
msgid "Templates"
msgstr ""
+msgid "Terminal"
+msgstr ""
+
msgid "Terminal for environment"
msgstr ""
@@ -10346,6 +10471,9 @@ msgstr ""
msgid "There are no issues to show"
msgstr ""
+msgid "There are no issues to show."
+msgstr ""
+
msgid "There are no labels yet"
msgstr ""
@@ -10478,6 +10606,9 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This feature requires local storage to be enabled"
+msgstr ""
+
msgid "This field is required."
msgstr ""
@@ -10848,6 +10979,9 @@ msgstr ""
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
msgstr ""
+msgid "To Do"
+msgstr ""
+
msgid "To GitLab"
msgstr ""
@@ -11136,6 +11270,12 @@ msgstr ""
msgid "Unfortunately, your email message to GitLab could not be processed."
msgstr ""
+msgid "Uninstall"
+msgstr ""
+
+msgid "Uninstalling"
+msgstr ""
+
msgid "Unknown encryption strategy: %{encrypted_strategy}!"
msgstr ""
@@ -11241,6 +11381,9 @@ msgstr ""
msgid "Updated"
msgstr ""
+msgid "Updated to"
+msgstr ""
+
msgid "Updating"
msgstr ""
@@ -11990,6 +12133,9 @@ msgstr ""
msgid "You don't have any deployments right now."
msgstr ""
+msgid "You don't have any recent searches"
+msgstr ""
+
msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}."
msgstr ""
@@ -12011,6 +12157,12 @@ msgstr ""
msgid "You have reached your project limit"
msgstr ""
+msgid "You haven't added any issues to your project yet"
+msgstr ""
+
+msgid "You haven't selected any issues yet"
+msgstr ""
+
msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
msgstr ""
@@ -12668,6 +12820,9 @@ msgstr ""
msgid "none"
msgstr ""
+msgid "not found"
+msgstr ""
+
msgid "notification emails"
msgstr ""
@@ -12717,6 +12872,9 @@ msgstr ""
msgid "register"
msgstr ""
+msgid "released %{time}"
+msgstr ""
+
msgid "remaining"
msgstr ""
@@ -12782,6 +12940,9 @@ msgstr ""
msgid "this document"
msgstr ""
+msgid "to list"
+msgstr ""
+
msgid "triggered"
msgstr ""
diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po
index 880d0f2e9a9..50d4d42f36a 100644
--- a/locale/gl_ES/gitlab.po
+++ b/locale/gl_ES/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po
index dcfe3ceb8a3..dc6ff543726 100644
--- a/locale/he_IL/gitlab.po
+++ b/locale/he_IL/gitlab.po
@@ -7477,13 +7477,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/hi_IN/gitlab.po b/locale/hi_IN/gitlab.po
index 76b060f25c3..126759c5828 100644
--- a/locale/hi_IN/gitlab.po
+++ b/locale/hi_IN/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/hr_HR/gitlab.po b/locale/hr_HR/gitlab.po
index ae4a40dd8f7..1f3242a6731 100644
--- a/locale/hr_HR/gitlab.po
+++ b/locale/hr_HR/gitlab.po
@@ -7425,13 +7425,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/hu_HU/gitlab.po b/locale/hu_HU/gitlab.po
index 131782290b2..142845c24a7 100644
--- a/locale/hu_HU/gitlab.po
+++ b/locale/hu_HU/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po
index f1680909623..be6c29d933c 100644
--- a/locale/id_ID/gitlab.po
+++ b/locale/id_ID/gitlab.po
@@ -7321,13 +7321,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index 02fa65dfa10..9b3ec180972 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index 8964d2081bf..cc27cb06364 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -7321,13 +7321,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr "JIRA APIのURL"
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr "課題がコミットで参照されると Jiraコメントが作成されます。"
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/ka_GE/gitlab.po b/locale/ka_GE/gitlab.po
index 5510c93d891..f6d768828ca 100644
--- a/locale/ka_GE/gitlab.po
+++ b/locale/ka_GE/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index e9ae48c0a96..6138874ac7d 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -7321,13 +7321,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/mn_MN/gitlab.po b/locale/mn_MN/gitlab.po
index 3e127d662ab..11bb2a40d14 100644
--- a/locale/mn_MN/gitlab.po
+++ b/locale/mn_MN/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/nb_NO/gitlab.po b/locale/nb_NO/gitlab.po
index 1a65c08fd32..7f150c167ee 100644
--- a/locale/nb_NO/gitlab.po
+++ b/locale/nb_NO/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po
index a21a659f33d..faa40d15de9 100644
--- a/locale/nl_NL/gitlab.po
+++ b/locale/nl_NL/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/pa_IN/gitlab.po b/locale/pa_IN/gitlab.po
index f24100b1dc5..723f2d4cb22 100644
--- a/locale/pa_IN/gitlab.po
+++ b/locale/pa_IN/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po
index 00fa4c3f03c..e3b257d8c16 100644
--- a/locale/pl_PL/gitlab.po
+++ b/locale/pl_PL/gitlab.po
@@ -7477,13 +7477,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index b5dab7c6379..0a1e2508d66 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr "Eventos para %{noteable_model_name} estão desabilitados."
msgid "JiraService|If different from Web URL"
msgstr "Se diferente do URL da Web"
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr "URL da API do JIRA"
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/pt_PT/gitlab.po b/locale/pt_PT/gitlab.po
index c9921faf129..f9174152adf 100644
--- a/locale/pt_PT/gitlab.po
+++ b/locale/pt_PT/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po
index 3d77b1552b4..71e8f9b199d 100644
--- a/locale/ro_RO/gitlab.po
+++ b/locale/ro_RO/gitlab.po
@@ -7425,13 +7425,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index 521c7ea6203..bb9e59a36a4 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -7477,13 +7477,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/sk_SK/gitlab.po b/locale/sk_SK/gitlab.po
index 763bb4a46f8..0b014f91082 100644
--- a/locale/sk_SK/gitlab.po
+++ b/locale/sk_SK/gitlab.po
@@ -7477,13 +7477,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po
index d136efa8675..2e43589b1fc 100644
--- a/locale/sq_AL/gitlab.po
+++ b/locale/sq_AL/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/sr_CS/gitlab.po b/locale/sr_CS/gitlab.po
index 4b89734ad03..34ba686fc45 100644
--- a/locale/sr_CS/gitlab.po
+++ b/locale/sr_CS/gitlab.po
@@ -7425,13 +7425,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/sr_SP/gitlab.po b/locale/sr_SP/gitlab.po
index 8a796c7d2c4..cefbe0910c4 100644
--- a/locale/sr_SP/gitlab.po
+++ b/locale/sr_SP/gitlab.po
@@ -7425,13 +7425,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/sv_SE/gitlab.po b/locale/sv_SE/gitlab.po
index 5fc6478d022..33c75c49a0f 100644
--- a/locale/sv_SE/gitlab.po
+++ b/locale/sv_SE/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/sw_KE/gitlab.po b/locale/sw_KE/gitlab.po
index 3b0ac677db7..63860064d3c 100644
--- a/locale/sw_KE/gitlab.po
+++ b/locale/sw_KE/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po
index a35ff0ce09f..3b552a3cbda 100644
--- a/locale/tr_TR/gitlab.po
+++ b/locale/tr_TR/gitlab.po
@@ -7373,13 +7373,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index b802c6fa323..ee9d9bc0fee 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -7477,13 +7477,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 77064035305..bfa6064303c 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -7321,13 +7321,13 @@ msgstr "%{noteable_model_name} 事件已禁用。"
msgid "JiraService|If different from Web URL"
msgstr "如果与 Web URL 不同"
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr "JIRA API URL"
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr "在提交中引用议题时将创建 JIRA 评论。"
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr "在合并请求中引用议题时将创建 JIRA 评论。"
msgid "JiraService|Jira issue tracker"
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 8485e17cd0d..541978fd726 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -7321,13 +7321,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index e7be5df3c80..22de348bdaa 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -7321,13 +7321,13 @@ msgstr ""
msgid "JiraService|If different from Web URL"
msgstr ""
-msgid "JiraService|JIRA API URL"
+msgid "JiraService|Jira API URL"
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a commit."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a commit."
msgstr ""
-msgid "JiraService|JIRA comments will be created when an issue gets referenced in a merge request."
+msgid "JiraService|Jira comments will be created when an issue gets referenced in a merge request."
msgstr ""
msgid "JiraService|Jira issue tracker"
diff --git a/package.json b/package.json
index ce3e5bd4490..da2ac7d64ef 100644
--- a/package.json
+++ b/package.json
@@ -37,8 +37,8 @@
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.4.4",
"@gitlab/csslab": "^1.9.0",
- "@gitlab/svgs": "^1.65.0",
- "@gitlab/ui": "^4.3.0",
+ "@gitlab/svgs": "^1.66.0",
+ "@gitlab/ui": "^5.1.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
diff --git a/qa/qa.rb b/qa/qa.rb
index 944dcc31917..10d44b6f6d9 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -162,9 +162,12 @@ module QA
module File
autoload :Form, 'qa/page/file/form'
autoload :Show, 'qa/page/file/show'
+ autoload :Edit, 'qa/page/file/edit'
module Shared
autoload :CommitMessage, 'qa/page/file/shared/commit_message'
+ autoload :CommitButton, 'qa/page/file/shared/commit_button'
+ autoload :Editor, 'qa/page/file/shared/editor'
end
end
@@ -218,6 +221,7 @@ module QA
autoload :Operations, 'qa/page/project/sub_menus/operations'
autoload :Repository, 'qa/page/project/sub_menus/repository'
autoload :Settings, 'qa/page/project/sub_menus/settings'
+ autoload :Project, 'qa/page/project/sub_menus/project'
end
module Issue
@@ -323,6 +327,7 @@ module QA
autoload :DropdownFilter, 'qa/page/component/dropdown_filter'
autoload :UsersSelect, 'qa/page/component/users_select'
autoload :Note, 'qa/page/component/note'
+ autoload :ConfirmModal, 'qa/page/component/confirm_modal'
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
diff --git a/qa/qa/page/component/confirm_modal.rb b/qa/qa/page/component/confirm_modal.rb
new file mode 100644
index 00000000000..355e2783fb7
--- /dev/null
+++ b/qa/qa/page/component/confirm_modal.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module ConfirmModal
+ def self.included(base)
+ base.view 'app/views/shared/_confirm_modal.html.haml' do
+ element :confirm_modal
+ element :confirm_input
+ element :confirm_button
+ end
+ end
+
+ def fill_confirmation_text(text)
+ fill_element :confirm_input, text
+ end
+
+ def click_confirm_button
+ click_element :confirm_button
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb
index e40bc4b1d3e..85d4abcde9b 100644
--- a/qa/qa/page/component/select2.rb
+++ b/qa/qa/page/component/select2.rb
@@ -18,6 +18,10 @@ module QA
find('.select2-input').set(item_text)
select_item(item_text)
end
+
+ def expand_select_list
+ find('span.select2-arrow').click
+ end
end
end
end
diff --git a/qa/qa/page/file/edit.rb b/qa/qa/page/file/edit.rb
new file mode 100644
index 00000000000..3a4a1837b1c
--- /dev/null
+++ b/qa/qa/page/file/edit.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module File
+ class Edit < Page::Base
+ include Shared::CommitMessage
+ include Shared::CommitButton
+ include Shared::Editor
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb
index e42de7d65c5..a6251f185f9 100644
--- a/qa/qa/page/file/form.rb
+++ b/qa/qa/page/file/form.rb
@@ -6,14 +6,11 @@ module QA
class Form < Page::Base
include Shared::CommitMessage
include Page::Component::DropdownFilter
+ include Shared::CommitButton
+ include Shared::Editor
view 'app/views/projects/blob/_editor.html.haml' do
element :file_name, "text_field_tag 'file_name'" # rubocop:disable QA/ElementWithPattern
- element :editor, '#editor' # rubocop:disable QA/ElementWithPattern
- end
-
- view 'app/views/projects/_commit_button.html.haml' do
- element :commit_changes, "button_tag 'Commit changes'" # rubocop:disable QA/ElementWithPattern
end
view 'app/views/projects/blob/_template_selectors.html.haml' do
@@ -28,20 +25,6 @@ module QA
fill_in 'file_name', with: name
end
- def add_content(content)
- text_area.set content
- end
-
- def remove_content
- text_area.send_keys([:command, 'a'], :backspace)
- end
-
- def commit_changes
- click_on 'Commit changes'
-
- finished_loading?
- end
-
def select_template(template_type, template)
click_element :template_type_dropdown
click_link template_type
@@ -60,12 +43,6 @@ module QA
end
filter_and_select template
end
-
- private
-
- def text_area
- find('#editor>textarea', visible: false)
- end
end
end
end
diff --git a/qa/qa/page/file/shared/commit_button.rb b/qa/qa/page/file/shared/commit_button.rb
new file mode 100644
index 00000000000..d8e751dd7b6
--- /dev/null
+++ b/qa/qa/page/file/shared/commit_button.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module File
+ module Shared
+ module CommitButton
+ def self.included(base)
+ base.view 'app/views/projects/_commit_button.html.haml' do
+ element :commit_button
+ end
+ end
+
+ def commit_changes
+ click_element(:commit_button)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/file/shared/editor.rb b/qa/qa/page/file/shared/editor.rb
new file mode 100644
index 00000000000..448c09cfbca
--- /dev/null
+++ b/qa/qa/page/file/shared/editor.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module File
+ module Shared
+ module Editor
+ def self.included(base)
+ base.view 'app/views/projects/blob/_editor.html.haml' do
+ element :editor
+ end
+ end
+
+ def add_content(content)
+ text_area.set content
+ end
+
+ def remove_content
+ text_area.send_keys([:command, 'a'], :backspace)
+ end
+
+ private
+
+ def text_area
+ within_element :editor do
+ find('textarea', visible: false)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb
index eaf88c6e69e..92f9181f99d 100644
--- a/qa/qa/page/file/show.rb
+++ b/qa/qa/page/file/show.rb
@@ -5,6 +5,8 @@ module QA
module File
class Show < Page::Base
include Shared::CommitMessage
+ include Project::SubMenus::Settings
+ include Project::SubMenus::Common
view 'app/helpers/blob_helper.rb' do
element :edit_button, "_('Edit')" # rubocop:disable QA/ElementWithPattern
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index 75530832860..ab4e3d757b6 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -5,9 +5,13 @@ module QA
module Project
module Settings
class Advanced < Page::Base
+ include Component::Select2
+ include Component::ConfirmModal
+
view 'app/views/projects/edit.html.haml' do
element :project_path_field
element :change_path_button
+ element :transfer_button
end
def update_project_path_to(path)
@@ -22,6 +26,18 @@ module QA
def click_change_path_button
click_element :change_path_button
end
+
+ def select_transfer_option(namespace)
+ search_and_select(namespace)
+ end
+
+ def transfer_project!(project_name, namespace)
+ expand_select_list
+ select_transfer_option(namespace)
+ click_element(:transfer_button)
+ fill_confirmation_text(project_name)
+ click_confirm_button
+ end
end
end
end
diff --git a/qa/qa/page/project/settings/main.rb b/qa/qa/page/project/settings/main.rb
index d1f3b15f950..dbbe62e3b1d 100644
--- a/qa/qa/page/project/settings/main.rb
+++ b/qa/qa/page/project/settings/main.rb
@@ -6,6 +6,8 @@ module QA
module Settings
class Main < Page::Base
include Common
+ include Component::Select2
+ include SubMenus::Project
view 'app/views/projects/edit.html.haml' do
element :advanced_settings
diff --git a/qa/qa/page/project/sub_menus/project.rb b/qa/qa/page/project/sub_menus/project.rb
new file mode 100644
index 00000000000..5e0ee3c274a
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/project.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Project
+ include Common
+
+ def self.included(base)
+ base.class_eval do
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :link_project
+ end
+ end
+ end
+
+ def click_project
+ retry_on_exception do
+ within_sidebar do
+ click_element(:link_project)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/settings.rb b/qa/qa/page/project/sub_menus/settings.rb
index 22743ebd0a1..88b45ec55ae 100644
--- a/qa/qa/page/project/sub_menus/settings.rb
+++ b/qa/qa/page/project/sub_menus/settings.rb
@@ -10,6 +10,7 @@ module QA
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :settings_item
element :link_members_settings
+ element :general_settings_link
end
end
end
@@ -38,6 +39,14 @@ module QA
end
end
+ def go_to_general_settings
+ hover_settings do
+ within_submenu do
+ click_element :general_settings_link
+ end
+ end
+ end
+
def click_settings
within_sidebar do
click_on 'Settings'
diff --git a/qa/qa/page/settings/common.rb b/qa/qa/page/settings/common.rb
index 8cd0b6bb49c..bede3fde105 100644
--- a/qa/qa/page/settings/common.rb
+++ b/qa/qa/page/settings/common.rb
@@ -11,7 +11,7 @@ module QA
within_element(element_name) do
# Because it is possible to click the button before the JS toggle code is bound
wait(reload: false) do
- click_button 'Expand' unless first('button', text: 'Collapse')
+ click_button 'Expand' unless has_css?('button', text: 'Collapse')
has_content?('Collapse')
end
diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb
index de04467ff5b..d1d75b6179e 100644
--- a/qa/qa/resource/api_fabricator.rb
+++ b/qa/qa/resource/api_fabricator.rb
@@ -13,6 +13,8 @@ module QA
ResourceURLMissingError = Class.new(RuntimeError)
attr_reader :api_resource, :api_response
+ attr_writer :api_client
+ attr_accessor :user
def api_support?
respond_to?(:api_get_path) &&
@@ -29,9 +31,12 @@ module QA
end
def eager_load_api_client!
+ return unless api_client.nil?
+
api_client.tap do |client|
# Eager-load the API client so that the personal token creation isn't
# taken in account in the actual resource creation timing.
+ client.user = user
client.personal_access_token
end
end
@@ -76,7 +81,7 @@ module QA
def api_client
@api_client ||= begin
- Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http'))
+ Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http'), user: user)
end
end
diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb
index 0b567a474c8..44d9dc8f296 100644
--- a/qa/qa/resource/group.rb
+++ b/qa/qa/resource/group.rb
@@ -67,6 +67,10 @@ module QA
visibility: 'public'
}
end
+
+ def full_path
+ sandbox.path + ' / ' + path
+ end
end
end
end
diff --git a/qa/qa/resource/merge_request_from_fork.rb b/qa/qa/resource/merge_request_from_fork.rb
index 5d20a6e9c75..6c9a096289b 100644
--- a/qa/qa/resource/merge_request_from_fork.rb
+++ b/qa/qa/resource/merge_request_from_fork.rb
@@ -21,7 +21,7 @@ module QA
def fabricate!
populate(:push)
- fork.visit!
+ fork.project.visit!
Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform(&:create_merge_request)
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index e8ea947581a..c0a6004fe27 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -11,7 +11,9 @@ module QA
attribute :id
attribute :name
+ attribute :add_name_uuid
attribute :description
+ attribute :standalone
attribute :group do
Group.fabricate!
@@ -38,18 +40,21 @@ module QA
end
def initialize
+ @add_name_uuid = true
+ @standalone = false
@description = 'My awesome project'
@initialize_with_readme = false
end
def name=(raw_name)
- @name = "#{raw_name}-#{SecureRandom.hex(8)}"
+ @name = @add_name_uuid ? "#{raw_name}-#{SecureRandom.hex(8)}" : raw_name
end
def fabricate!
- group.visit!
-
- Page::Group::Show.perform(&:go_to_new_project)
+ unless @standalone
+ group.visit!
+ Page::Group::Show.perform(&:go_to_new_project)
+ end
Page::Project::New.perform do |page|
page.choose_test_namespace
@@ -71,19 +76,28 @@ module QA
"/projects/#{CGI.escape(path_with_namespace)}"
end
+ def api_get_archive_path(type = 'tar.gz')
+ "#{api_get_path}/repository/archive.#{type}"
+ end
+
def api_post_path
'/projects'
end
def api_post_body
- {
- namespace_id: group.id,
- path: name,
+ post_body = {
name: name,
description: description,
visibility: 'public',
initialize_with_readme: @initialize_with_readme
}
+
+ unless @standalone
+ post_body[:namespace_id] = group.id
+ post_body[:path] = name
+ end
+
+ post_body
end
private
diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb
index 6c5e91b6488..eec46f46d99 100644
--- a/qa/qa/resource/user.rb
+++ b/qa/qa/resource/user.rb
@@ -88,7 +88,7 @@ module QA
}.merge(ldap_post_body)
end
- def self.fabricate_or_use(username, password)
+ def self.fabricate_or_use(username = nil, password = nil)
if Runtime::Env.signup_disabled?
self.new.tap do |user|
user.username = username
diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb
index 40a3bc85195..663be27a849 100644
--- a/qa/qa/runtime/api/client.rb
+++ b/qa/qa/runtime/api/client.rb
@@ -6,31 +6,34 @@ module QA
module Runtime
module API
class Client
- attr_reader :address
+ attr_reader :address, :user
- def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true)
+ def initialize(address = :gitlab, personal_access_token: nil, is_new_session: true, user: nil)
@address = address
@personal_access_token = personal_access_token
@is_new_session = is_new_session
+ @user = user
end
def personal_access_token
@personal_access_token ||= begin
# 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
+ # unless a specific user has been passed
+ @user.nil? ? Runtime::Env.personal_access_token ||= create_personal_access_token : create_personal_access_token
end
end
private
def create_personal_access_token
- Runtime::Browser.visit(@address, Page::Main::Login) if @is_new_session
- do_create_personal_access_token
- end
+ Page::Main::Menu.perform(&:sign_out) if @is_new_session && Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
+
+ unless Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
+ Runtime::Browser.visit(@address, Page::Main::Login)
+ Page::Main::Login.perform { |login| login.sign_in_using_credentials(@user) }
+ end
- def do_create_personal_access_token
- Page::Main::Login.perform(&:sign_in_using_credentials)
Resource::PersonalAccessToken.fabricate!.access_token
end
end
diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb
index 7065ab0e7f3..217df669db3 100644
--- a/qa/qa/service/shellout.rb
+++ b/qa/qa/service/shellout.rb
@@ -19,7 +19,7 @@ module QA
Open3.popen2e(*command) do |stdin, out, wait|
stdin.puts(stdin_data) if stdin_data
stdin.close if stdin_data
- out.each { |line| puts line }
+ out.each_char { |char| print char }
if wait.value.exited? && wait.value.exitstatus.nonzero?
raise CommandError, "Command `#{command}` failed!"
diff --git a/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
new file mode 100644
index 00000000000..3fe04e8b835
--- /dev/null
+++ b/qa/qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'securerandom'
+require 'digest'
+
+module QA
+ context 'Create' do
+ describe 'Compare archives of different user projects with the same name and check they\'re different' do
+ include Support::Api
+
+ before(:all) do
+ @project_name = "project-archive-download-#{SecureRandom.hex(8)}"
+ @archive_types = %w(tar.gz tar.bz2 tar zip)
+ @users = {
+ user1: { username: Runtime::Env.gitlab_qa_username_1, password: Runtime::Env.gitlab_qa_password_1 },
+ user2: { username: Runtime::Env.gitlab_qa_username_2, password: Runtime::Env.gitlab_qa_password_2 }
+ }
+
+ @users.each do |_, user_info|
+ user_info[:user] = Resource::User.fabricate_or_use(user_info[:username], user_info[:password])
+ user_info[:api_client] = Runtime::API::Client.new(:gitlab, user: user_info[:user])
+ user_info[:api_client].personal_access_token
+ user_info[:project] = create_project(user_info[:user], user_info[:api_client], @project_name)
+ Page::Main::Menu.perform(&:sign_out)
+ end
+ end
+
+ it 'download archives of each user project then check they are different' do
+ archive_checksums = {}
+
+ @users.each do |user_key, user_info|
+ archive_checksums[user_key] = {}
+
+ @archive_types.each do |type|
+ archive_path = download_project_archive_via_api(user_info[:api_client], user_info[:project], type).path
+ archive_checksums[user_key][type] = Digest::MD5.hexdigest(File.read(archive_path))
+ end
+ end
+
+ QA::Runtime::Logger.debug("Archive checksums are #{archive_checksums}")
+
+ expect(archive_checksums[:user1]).not_to include(archive_checksums[:user2])
+ end
+
+ def create_project(user, api_client, project_name)
+ project = Resource::Project.fabricate! do |project|
+ project.standalone = true
+ project.add_name_uuid = false
+ project.name = project_name
+ project.path_with_namespace = "#{user.name}/#{project_name}"
+ project.user = user
+ project.api_client = api_client
+ end
+
+ Resource::Repository::ProjectPush.fabricate! do |push|
+ push.project = project
+ push.file_name = 'README.md'
+ push.file_content = '# This is a test project'
+ push.commit_message = 'Add README.md'
+ push.user = user
+ end
+
+ project
+ end
+
+ def download_project_archive_via_api(api_client, project, type = 'tar.gz')
+ get_project_archive_zip = Runtime::API::Request.new(api_client, project.api_get_archive_path(type))
+ project_archive_download = get(get_project_archive_zip.url, raw_response: true)
+ expect(project_archive_download.code).to eq(200)
+
+ project_archive_download.file
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
new file mode 100644
index 00000000000..a9de64e357a
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Manage' do
+ describe 'Project transfer between groups' do
+ it 'user transfers a project between groups' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ source_group = Resource::Group.fabricate! do |group|
+ group.path = 'source-group'
+ end
+
+ target_group = Resource::Group.fabricate! do |group|
+ group.path = 'target-group'
+ end
+
+ project = Resource::Project.fabricate! do |project|
+ project.group = source_group
+ project.name = 'transfer-project'
+ project.initialize_with_readme = true
+ end
+
+ project.visit!
+
+ Page::Project::Show.perform do |project|
+ project.click_file('README.md')
+ end
+
+ Page::File::Show.perform(&:click_edit)
+
+ edited_readme_content = 'Here is the edited content.'
+
+ Page::File::Edit.perform do |file|
+ file.remove_content
+ file.add_content(edited_readme_content)
+ file.commit_changes
+ end
+
+ Page::File::Show.perform(&:go_to_general_settings)
+
+ Page::Project::Settings::Main.perform(&:expand_advanced_settings)
+
+ Page::Project::Settings::Advanced.perform do |advanced|
+ advanced.transfer_project!(project.name, target_group.full_path)
+ end
+
+ Page::Project::Settings::Main.perform(&:click_project)
+
+ Page::Project::Show.perform do |project|
+ expect(project).to have_text(target_group.path)
+ expect(project).to have_text(edited_readme_content)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb
index a5c86425465..203064b2665 100644
--- a/qa/qa/support/api.rb
+++ b/qa/qa/support/api.rb
@@ -16,11 +16,12 @@ module QA
e.response
end
- def get(url)
+ def get(url, raw_response: false)
RestClient::Request.execute(
method: :get,
url: url,
- verify_ssl: false)
+ verify_ssl: false,
+ raw_response: raw_response)
rescue RestClient::ExceptionWithResponse => e
e.response
end
diff --git a/qa/spec/runtime/api/client_spec.rb b/qa/spec/runtime/api/client_spec.rb
index cf19b52700b..6f7020d6595 100644
--- a/qa/spec/runtime/api/client_spec.rb
+++ b/qa/spec/runtime/api/client_spec.rb
@@ -16,26 +16,56 @@ describe QA::Runtime::API::Client do
end
describe '#personal_access_token' do
- context 'when QA::Runtime::Env.personal_access_token is present' do
+ context 'when user is nil and QA::Runtime::Env.personal_access_token is present' do
before do
allow(QA::Runtime::Env).to receive(:personal_access_token).and_return('a_token')
end
it 'returns specified token from env' do
- expect(described_class.new.personal_access_token).to eq 'a_token'
+ expect(subject.personal_access_token).to eq 'a_token'
end
end
- context 'when QA::Runtime::Env.personal_access_token is nil' do
+ context 'when user is present and QA::Runtime::Env.personal_access_token is nil' do
before do
allow(QA::Runtime::Env).to receive(:personal_access_token).and_return(nil)
end
it 'returns a created token' do
+ subject { described_class.new(user: { username: 'foo' }) }
+
expect(subject).to receive(:create_personal_access_token).and_return('created_token')
expect(subject.personal_access_token).to eq 'created_token'
end
end
+
+ context 'when user is nil and QA::Runtime::Env.personal_access_token is nil' do
+ before do
+ allow(QA::Runtime::Env).to receive(:personal_access_token).and_return(nil)
+ end
+
+ it 'returns a created token' do
+ client = described_class.new
+
+ expect(client).to receive(:create_personal_access_token).and_return('created_token')
+
+ expect(client.personal_access_token).to eq 'created_token'
+ end
+ end
+
+ context 'when user is present and QA::Runtime::Env.personal_access_token is present' do
+ before do
+ allow(QA::Runtime::Env).to receive(:personal_access_token).and_return('a_token')
+ end
+
+ it 'returns a created token' do
+ client = described_class.new(user: { username: 'foo' })
+
+ expect(client).to receive(:create_personal_access_token).and_return('created_token')
+
+ expect(client.personal_access_token).to eq 'created_token'
+ end
+ end
end
end
diff --git a/scripts/generate-memory-metrics-on-boot b/scripts/generate-memory-metrics-on-boot
new file mode 100755
index 00000000000..5197a8fcdcd
--- /dev/null
+++ b/scripts/generate-memory-metrics-on-boot
@@ -0,0 +1,11 @@
+#!/usr/bin/env ruby
+
+abort "usage: #{__FILE__} <memory_bundle_mem_file_name>" unless ARGV.length == 1
+memory_bundle_mem_file_name = ARGV.first
+
+full_report = File.open(memory_bundle_mem_file_name).read
+
+stats = /TOP: (?<total_mibs_str>.*) MiB/.match(full_report)
+abort 'failed to process the benchmark output' unless stats
+
+puts "total_memory_used_by_dependencies_on_boot_prod_env_mb #{stats[:total_mibs_str].to_f.round(1)}"
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 9b0d5d4f719..0950ec272a5 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -35,9 +35,11 @@ sed -i 's/username: root/username: gitlab/g' config/database.yml
if [ "$GITLAB_DATABASE" = 'postgresql' ]; then
sed -i 's/localhost/postgres/g' config/database.yml
+ sed -i 's/username: git/username: postgres/g' config/database.yml
if [ -f config/database_geo.yml ]; then
sed -i 's/localhost/postgres/g' config/database_geo.yml
+ sed -i 's/username: git/username: postgres/g' config/database_geo.yml
fi
else # Assume it's mysql
sed -i 's/localhost/mysql/g' config/database.yml
@@ -48,16 +50,16 @@ else # Assume it's mysql
fi
cp config/resque.yml.example config/resque.yml
-sed -i 's/localhost/redis/g' config/resque.yml
+sed -i 's|url:.*$|url: redis://redis:6379|g' config/resque.yml
cp config/redis.cache.yml.example config/redis.cache.yml
-sed -i 's/localhost/redis/g' config/redis.cache.yml
+sed -i 's|url:.*$|url: redis://redis:6379/10|g' config/redis.cache.yml
cp config/redis.queues.yml.example config/redis.queues.yml
-sed -i 's/localhost/redis/g' config/redis.queues.yml
+sed -i 's|url:.*$|url: redis://redis:6379/11|g' config/redis.queues.yml
cp config/redis.shared_state.yml.example config/redis.shared_state.yml
-sed -i 's/localhost/redis/g' config/redis.shared_state.yml
+sed -i 's|url:.*$|url: redis://redis:6379/12|g' config/redis.shared_state.yml
if [ "$SETUP_DB" != "false" ]; then
setup_db
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 47d7e278183..d2faef5b12b 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -125,11 +125,14 @@ describe GroupsController do
end
context 'as json' do
- it 'includes all projects in event feed' do
- 3.times do
+ it 'includes all projects from groups and subgroups in event feed' do
+ 2.times do
project = create(:project, group: group)
create(:event, project: project)
end
+ subgroup = create(:group, parent: group)
+ project = create(:project, group: subgroup)
+ create(:event, project: project)
get :activity, params: { id: group.to_param }, format: :json
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index cf201c9f735..b30966e70a7 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -92,7 +92,7 @@ describe Projects::BranchesController do
end
it 'posts a system note' do
- expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch")
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch", branch_project: project)
post :create,
params: {
@@ -103,6 +103,75 @@ describe Projects::BranchesController do
}
end
+ context 'confidential_issue_project_id is present' do
+ let(:confidential_issue_project) { create(:project) }
+
+ def create_branch_with_confidential_issue_project
+ post(
+ :create,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ branch_name: branch,
+ confidential_issue_project_id: confidential_issue_project.id,
+ issue_iid: issue.iid
+ }
+ )
+ end
+
+ context 'create_confidential_merge_request feature is enabled' do
+ before do
+ stub_feature_flags(create_confidential_merge_request: true)
+ end
+
+ context 'user cannot update issue' do
+ let(:issue) { create(:issue, project: confidential_issue_project) }
+
+ it 'does not post a system note' do
+ expect(SystemNoteService).not_to receive(:new_issue_branch)
+
+ create_branch_with_confidential_issue_project
+ end
+ end
+
+ context 'user can update issue' do
+ before do
+ confidential_issue_project.add_reporter(user)
+ end
+
+ context 'issue is under the specified project' do
+ let(:issue) { create(:issue, project: confidential_issue_project) }
+
+ it 'posts a system note' do
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, confidential_issue_project, user, "1-feature-branch", branch_project: project)
+
+ create_branch_with_confidential_issue_project
+ end
+ end
+
+ context 'issue is not under the specified project' do
+ it 'does not post a system note' do
+ expect(SystemNoteService).not_to receive(:new_issue_branch)
+
+ create_branch_with_confidential_issue_project
+ end
+ end
+ end
+ end
+
+ context 'create_confidential_merge_request feature is disabled' do
+ before do
+ stub_feature_flags(create_confidential_merge_request: false)
+ end
+
+ it 'posts a system note on project' do
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, "1-feature-branch", branch_project: project)
+
+ create_branch_with_confidential_issue_project
+ end
+ end
+ end
+
context 'repository-less project' do
let(:project) { create :project }
@@ -507,4 +576,27 @@ describe Projects::BranchesController do
end
end
end
+
+ describe 'GET diverging_commit_counts' do
+ before do
+ sign_in(user)
+
+ get :diverging_commit_counts,
+ format: :json,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ names: ['fix', 'add-pdf-file', 'branch-merged']
+ }
+ end
+
+ it 'returns the commit counts behind and ahead of default branch' do
+ parsed_response = JSON.parse(response.body)
+ expect(parsed_response).to eq(
+ "fix" => { "behind" => 29, "ahead" => 2 },
+ "branch-merged" => { "behind" => 1, "ahead" => 0 },
+ "add-pdf-file" => { "behind" => 0, "ahead" => 3 }
+ )
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index f82e3c8c7dc..bc5e0b4671e 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::IssuesController do
+ include ProjectForksHelper
+
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
@@ -1130,6 +1132,7 @@ describe Projects::IssuesController do
end
describe 'POST create_merge_request' do
+ let(:target_project_id) { nil }
let(:project) { create(:project, :repository, :public) }
before do
@@ -1163,13 +1166,42 @@ describe Projects::IssuesController do
expect(response).to have_gitlab_http_status(404)
end
+ context 'target_project_id is set' do
+ let(:target_project) { fork_project(project, user, repository: true) }
+ let(:target_project_id) { target_project.id }
+
+ context 'create_confidential_merge_request feature is enabled' do
+ before do
+ stub_feature_flags(create_confidential_merge_request: true)
+ end
+
+ it 'creates a new merge request' do
+ expect { create_merge_request }.to change(target_project.merge_requests, :count).by(1)
+ end
+ end
+
+ context 'create_confidential_merge_request feature is disabled' do
+ before do
+ stub_feature_flags(create_confidential_merge_request: false)
+ end
+
+ it 'creates a new merge request' do
+ expect { create_merge_request }.to change(project.merge_requests, :count).by(1)
+ end
+ end
+ end
+
def create_merge_request
- post :create_merge_request, params: {
- namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- id: issue.to_param
- },
- format: :json
+ post(
+ :create_merge_request,
+ params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: issue.to_param,
+ target_project_id: target_project_id
+ },
+ format: :json
+ )
end
end
diff --git a/spec/controllers/projects/merge_requests/content_controller_spec.rb b/spec/controllers/projects/merge_requests/content_controller_spec.rb
new file mode 100644
index 00000000000..2879e06aee4
--- /dev/null
+++ b/spec/controllers/projects/merge_requests/content_controller_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::MergeRequests::ContentController do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request, target_project: project, source_project: project) }
+
+ before do
+ sign_in(user)
+ end
+
+ def do_request
+ get :widget, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.iid,
+ format: :json
+ }
+ end
+
+ describe 'GET widget' do
+ context 'user has access to the project' do
+ before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+
+ project.add_maintainer(user)
+ end
+
+ it 'renders widget MR entity as json' do
+ do_request
+
+ expect(response).to match_response_schema('entities/merge_request_widget')
+ end
+
+ it 'checks whether the MR can be merged' do
+ controller.instance_variable_set(:@merge_request, merge_request)
+
+ expect(merge_request).to receive(:check_mergeability)
+
+ do_request
+ end
+
+ it 'closes an MR with moved source project' do
+ merge_request.update_column(:source_project_id, nil)
+
+ expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false)
+ end
+ end
+
+ context 'user does not have access to the project' do
+ it 'renders widget MR entity as json' do
+ do_request
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 5c7f8d95f82..68eabce8513 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -128,7 +128,7 @@ describe Projects::ServicesController do
params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } }
expect(response).to redirect_to(project_settings_integrations_path(project))
- expect(flash[:notice]).to eq 'JIRA activated.'
+ expect(flash[:notice]).to eq 'Jira activated.'
end
end
@@ -137,17 +137,17 @@ describe Projects::ServicesController do
put :update,
params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: false } }
- expect(flash[:notice]).to eq 'JIRA settings saved, but not activated.'
+ expect(flash[:notice]).to eq 'Jira settings saved, but not activated.'
end
end
- context 'when activating JIRA service from a template' do
+ context 'when activating Jira service from a template' do
let(:template_service) { create(:jira_service, project: project, template: true) }
- it 'activate JIRA service from template' do
+ it 'activate Jira service from template' do
put :update, params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { active: true } }
- expect(flash[:notice]).to eq 'JIRA activated.'
+ expect(flash[:notice]).to eq 'Jira activated.'
end
end
end
diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb
index d37e2bf511e..43753fa650c 100644
--- a/spec/factories/award_emoji.rb
+++ b/spec/factories/award_emoji.rb
@@ -5,7 +5,7 @@ FactoryBot.define do
awardable factory: :issue
after(:create) do |award, evaluator|
- award.awardable.project.add_guest(evaluator.user)
+ award.awardable.project&.add_guest(evaluator.user)
end
trait :upvote
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index db438ad32d3..1c7787bc1a6 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -22,6 +22,10 @@ FactoryBot.define do
ref 'pages-deploy'
end
+ trait :on_cluster do
+ cluster factory: %i(cluster provided_by_gcp)
+ end
+
trait :running do
status :running
end
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 08fa4a98feb..260eec7a9ed 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -362,14 +362,14 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
end
end
- it 'shows jump to next discussion button except on last discussion' do
+ it 'shows jump to next discussion button on all discussions' do
wait_for_requests
all_discussion_replies = page.all('.discussion-reply-holder')
expect(all_discussion_replies.count).to eq(2)
expect(all_discussion_replies.first.all('.discussion-next-btn').count).to eq(1)
- expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(0)
+ expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(1)
end
it 'displays next discussion even if hidden' do
diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
index ff0aa933a3e..5bce96d9b80 100644
--- a/spec/features/projects/files/user_reads_pipeline_status_spec.rb
+++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
@@ -7,6 +7,8 @@ describe 'user reads pipeline status', :js do
let(:x110_pipeline) { create_pipeline('x1.1.0', 'failed') }
before do
+ stub_feature_flags(vue_file_list: false)
+
project.add_maintainer(user)
project.repository.add_tag(user, 'x1.1.0', 'v1.1.0')
diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb
index 08e1855d034..c52f38e2806 100644
--- a/spec/features/projects/services/user_activates_jira_spec.rb
+++ b/spec/features/projects/services/user_activates_jira_spec.rb
@@ -29,27 +29,27 @@ describe 'User activates Jira', :js do
server_info = { key: 'value' }.to_json
WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password)).to_return(body: server_info)
- click_link('JIRA')
+ click_link('Jira')
fill_form
click_button('Test settings and save changes')
wait_for_requests
end
- it 'activates the JIRA service' do
- expect(page).to have_content('JIRA activated.')
+ it 'activates the Jira service' do
+ expect(page).to have_content('Jira activated.')
expect(current_path).to eq(project_settings_integrations_path(project))
end
- it 'shows the JIRA link in the menu' do
+ it 'shows the Jira link in the menu' do
page.within('.nav-sidebar') do
- expect(page).to have_link('JIRA', href: url)
+ expect(page).to have_link('Jira', href: url)
end
end
end
context 'when Jira connection test fails' do
it 'shows errors when some required fields are not filled in' do
- click_link('JIRA')
+ click_link('Jira')
check 'Active'
fill_in 'service_password', with: 'password'
@@ -60,11 +60,11 @@ describe 'User activates Jira', :js do
end
end
- it 'activates the JIRA service' do
+ it 'activates the Jira service' do
WebMock.stub_request(:get, test_url).with(basic_auth: %w(username password))
.to_raise(JIRA::HTTPError.new(double(message: 'message')))
- click_link('JIRA')
+ click_link('Jira')
fill_form
click_button('Test settings and save changes')
wait_for_requests
@@ -75,7 +75,7 @@ describe 'User activates Jira', :js do
find('.flash-alert .flash-action').click
wait_for_requests
- expect(page).to have_content('JIRA activated.')
+ expect(page).to have_content('Jira activated.')
expect(current_path).to eq(project_settings_integrations_path(project))
end
end
@@ -83,19 +83,19 @@ describe 'User activates Jira', :js do
describe 'user sets Jira Service but keeps it disabled' do
before do
- click_link('JIRA')
+ click_link('Jira')
fill_form(false)
click_button('Save changes')
end
- it 'saves but does not activate the JIRA service' do
- expect(page).to have_content('JIRA settings saved, but not activated.')
+ it 'saves but does not activate the Jira service' do
+ expect(page).to have_content('Jira settings saved, but not activated.')
expect(current_path).to eq(project_settings_integrations_path(project))
end
- it 'does not show the JIRA link in the menu' do
+ it 'does not show the Jira link in the menu' do
page.within('.nav-sidebar') do
- expect(page).not_to have_link('JIRA', href: url)
+ expect(page).not_to have_link('Jira', href: url)
end
end
end
diff --git a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
index e277bfb8011..89ce4b50781 100644
--- a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
+++ b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
@@ -3,6 +3,10 @@ require 'spec_helper'
describe 'Projects > Show > User sees last commit CI status' do
set(:project) { create(:project, :repository, :public) }
+ before do
+ stub_feature_flags(vue_file_list: false)
+ end
+
it 'shows the project README', :js do
project.enable_ci
pipeline = create(:ci_pipeline, project: project, sha: project.commit.sha, ref: 'master')
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index 7d164539d9a..3fc86f3e408 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -62,6 +62,15 @@ describe BranchesFinder do
expect(result.count).to eq(0)
end
+
+ it 'filters branches by provided names' do
+ branches_finder = described_class.new(repository, { names: ['fix', 'csv', 'lfs', 'does-not-exist'] })
+
+ result = branches_finder.execute
+
+ expect(result.count).to eq(3)
+ expect(result.map(&:name)).to eq(%w{csv fix lfs})
+ end
end
context 'filter and sort' do
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 7018cb9a305..eac1dbc6474 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -99,7 +99,8 @@
"revert_in_fork_path": { "type": ["string", "null"] },
"email_patches_path": { "type": "string" },
"plain_diff_path": { "type": "string" },
- "status_path": { "type": "string" },
+ "merge_request_basic_path": { "type": "string" },
+ "merge_request_widget_path": { "type": "string" },
"new_blob_path": { "type": ["string", "null"] },
"merge_check_path": { "type": "string" },
"ci_environments_status_path": { "type": "string" },
diff --git a/spec/frontend/branches/divergence_graph_spec.js b/spec/frontend/branches/divergence_graph_spec.js
new file mode 100644
index 00000000000..4ed77c3a036
--- /dev/null
+++ b/spec/frontend/branches/divergence_graph_spec.js
@@ -0,0 +1,32 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import init from '~/branches/divergence_graph';
+
+describe('Divergence graph', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+
+ mock.onGet('/-/diverging_counts').reply(200, {
+ master: { ahead: 1, behind: 1 },
+ });
+
+ jest.spyOn(axios, 'get');
+
+ document.body.innerHTML = `
+ <div class="js-branch-item" data-name="master"></div>
+ `;
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('calls axos get with list of branch names', () =>
+ init('/-/diverging_counts').then(() => {
+ expect(axios.get).toHaveBeenCalledWith('/-/diverging_counts', {
+ params: { names: ['master'] },
+ });
+ }));
+});
diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
index 3ad6bfa9e5f..cd8372a8800 100644
--- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
+++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
@@ -27,8 +27,8 @@ exports[`Repository last commit component renders commit widget 1`] = `
href="https://test.com/commit/123"
>
- Commit title
-
+ Commit title
+
</gllink-stub>
<!---->
@@ -41,12 +41,12 @@ exports[`Repository last commit component renders commit widget 1`] = `
href="https://test.com/test"
>
- Test
-
+ Test
+
</gllink-stub>
- authored
-
+ authored
+
<timeagotooltip-stub
cssclass=""
time="2019-01-01"
@@ -81,8 +81,8 @@ exports[`Repository last commit component renders commit widget 1`] = `
class="label label-monospace monospace"
>
- 12345678
-
+ 12345678
+
</div>
<clipboardbutton-stub
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index 972690a60f6..14479f3c3a4 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
import LastCommit from '~/repository/components/last_commit.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
@@ -6,7 +7,7 @@ let vm;
function createCommitData(data = {}) {
return {
- id: '123456789',
+ sha: '123456789',
title: 'Commit title',
message: 'Commit message',
webUrl: 'https://test.com/commit/123',
@@ -16,7 +17,7 @@ function createCommitData(data = {}) {
avatarUrl: 'https://test.com',
webUrl: 'https://test.com/test',
},
- pipeline: {
+ latestPipeline: {
detailedStatus: {
detailsPath: 'https://test.com/pipeline',
icon: 'failed',
@@ -52,12 +53,12 @@ describe('Repository last commit component', () => {
it.each`
loading | label
- ${true} | ${'hides'}
- ${false} | ${'shows'}
- `('$label when $loading is true', ({ loading }) => {
+ ${true} | ${'shows'}
+ ${false} | ${'hides'}
+ `('$label when loading icon $loading is true', ({ loading }) => {
factory(createCommitData(), loading);
- expect(vm.isEmpty()).toBe(loading);
+ expect(vm.find(GlLoadingIcon).exists()).toBe(loading);
});
it('renders commit widget', () => {
@@ -73,11 +74,17 @@ describe('Repository last commit component', () => {
});
it('hides pipeline components when pipeline does not exist', () => {
- factory(createCommitData({ pipeline: null }));
+ factory(createCommitData({ latestPipeline: null }));
expect(vm.find('.js-commit-pipeline').exists()).toBe(false);
});
+ it('renders pipeline components', () => {
+ factory();
+
+ expect(vm.find('.js-commit-pipeline').exists()).toBe(true);
+ });
+
it('hides author component when author does not exist', () => {
factory(createCommitData({ author: null }));
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index c17d5253997..15cf18700ed 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -3,6 +3,7 @@ import * as jqueryMatchers from 'custom-jquery-matchers';
import $ from 'jquery';
import Translate from '~/vue_shared/translate';
import axios from '~/lib/utils/axios_utils';
+import { config as testUtilsConfig } from '@vue/test-utils';
import { initializeTestTimeout } from './helpers/timeout';
import { loadHTMLFixture, setHTMLFixture } from './helpers/fixtures';
@@ -60,9 +61,21 @@ Object.assign(global, {
preloadFixtures() {},
});
+Object.assign(global, {
+ MutationObserver() {
+ return {
+ disconnect() {},
+ observe() {},
+ };
+ },
+});
+
// 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,
});
});
+
+// Tech debt issue TBD
+testUtilsConfig.logModifiedComponents = false;
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index 4076c1f824b..d36e428a8ee 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -113,7 +113,7 @@ describe GitlabSchema do
end
it "raises a meaningful error if a global id couldn't be generated" do
- expect { described_class.id_from_object(build(:commit)) }
+ expect { described_class.id_from_object(build(:wiki_directory)) }
.to raise_error(RuntimeError, /include `GlobalID::Identification` into/i)
end
end
diff --git a/spec/graphql/types/award_emojis/award_emoji_type_spec.rb b/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
new file mode 100644
index 00000000000..5663a3d7195
--- /dev/null
+++ b/spec/graphql/types/award_emojis/award_emoji_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['AwardEmoji'] do
+ it { expect(described_class.graphql_name).to eq('AwardEmoji') }
+
+ it { is_expected.to require_graphql_authorizations(:read_emoji) }
+
+ it { expect(described_class).to have_graphql_fields(:description, :unicode_version, :emoji, :name, :unicode, :user) }
+end
diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb
new file mode 100644
index 00000000000..5d8edcf254c
--- /dev/null
+++ b/spec/graphql/types/commit_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['Commit'] do
+ it { expect(described_class.graphql_name).to eq('Commit') }
+
+ it { expect(described_class).to require_graphql_authorizations(:download_code) }
+
+ it { expect(described_class).to have_graphql_fields(:id, :sha, :title, :description, :message, :authored_date, :author, :web_url, :latest_pipeline) }
+end
diff --git a/spec/graphql/types/tree/tree_type_spec.rb b/spec/graphql/types/tree/tree_type_spec.rb
index b9c5570115e..23779d75600 100644
--- a/spec/graphql/types/tree/tree_type_spec.rb
+++ b/spec/graphql/types/tree/tree_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
describe Types::Tree::TreeType do
it { expect(described_class.graphql_name).to eq('Tree') }
- it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs) }
+ it { expect(described_class).to have_graphql_fields(:trees, :submodules, :blobs, :last_commit) }
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 597c8f836a9..6c6410cee9b 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -50,6 +50,43 @@ describe MarkupHelper do
expect(markdown(actual, project: second_project)).to match(expected)
end
end
+
+ describe 'uploads' do
+ let(:text) { "![ImageTest](/uploads/test.png)" }
+ let(:group) { create(:group) }
+
+ subject { helper.markdown(text) }
+
+ describe 'inside a project' do
+ it 'renders uploads relative to project' do
+ expect(subject).to include("#{project.full_path}/uploads/test.png")
+ end
+ end
+
+ describe 'inside a group' do
+ before do
+ helper.instance_variable_set(:@group, group)
+ helper.instance_variable_set(:@project, nil)
+ end
+
+ it 'renders uploads relative to the group' do
+ expect(subject).to include("#{group.full_path}/-/uploads/test.png")
+ end
+ end
+
+ describe "with a group in the context" do
+ let(:project_in_group) { create(:project, group: group) }
+
+ before do
+ helper.instance_variable_set(:@group, group)
+ helper.instance_variable_set(:@project, project_in_group)
+ end
+
+ it 'renders uploads relative to project' do
+ expect(subject).to include("#{project_in_group.path_with_namespace}/uploads/test.png")
+ end
+ end
+ end
end
describe '#markdown_field' do
diff --git a/spec/helpers/onboarding_experiment_helper_spec.rb b/spec/helpers/onboarding_experiment_helper_spec.rb
new file mode 100644
index 00000000000..5b7d9b1c2e6
--- /dev/null
+++ b/spec/helpers/onboarding_experiment_helper_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe OnboardingExperimentHelper, type: :helper do
+ describe '.allow_access_to_onboarding?' do
+ context "when we're not gitlab.com" do
+ it 'returns false' do
+ allow(::Gitlab).to receive(:com?).and_return(false)
+
+ expect(helper.allow_access_to_onboarding?).to be(false)
+ end
+ end
+
+ context "when we're gitlab.com" do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(true)
+ end
+
+ context 'and the :user_onboarding feature is not enabled' do
+ it 'returns false' do
+ stub_feature_flags(user_onboarding: false)
+
+ expect(helper.allow_access_to_onboarding?).to be(false)
+ end
+ end
+
+ context 'and the :user_onboarding feature is enabled' do
+ it 'returns true' do
+ stub_feature_flags(user_onboarding: true)
+ allow(helper).to receive(:current_user).and_return(create(:user))
+
+ expect(helper.allow_access_to_onboarding?).to be(true)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb
index f96e5a2133f..73fbd4c7a44 100644
--- a/spec/initializers/6_validations_spec.rb
+++ b/spec/initializers/6_validations_spec.rb
@@ -2,16 +2,6 @@ require 'spec_helper'
require_relative '../../config/initializers/6_validations.rb'
describe '6_validations' do
- before :all do
- FileUtils.mkdir_p('tmp/tests/paths/a/b/c/d')
- FileUtils.mkdir_p('tmp/tests/paths/a/b/c2')
- FileUtils.mkdir_p('tmp/tests/paths/a/b/d')
- end
-
- after :all do
- FileUtils.rm_rf('tmp/tests/paths')
- end
-
describe 'validate_storages_config' do
context 'with correct settings' do
before do
@@ -23,16 +13,6 @@ describe '6_validations' do
end
end
- context 'when one of the settings is incorrect' do
- before do
- mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c', 'failure_count_threshold' => 'not a number'))
- end
-
- it 'throws an error' do
- expect { validate_storages_config }.to raise_error(/failure_count_threshold/)
- end
- end
-
context 'with invalid storage names' do
before do
mock_storages('name with spaces' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'))
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
index d08ee41802b..683783334c6 100644
--- a/spec/javascripts/boards/components/board_spec.js
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
-import '~/boards/services/board_service';
import Board from '~/boards/components/board';
-import '~/boards/models/list';
+import List from '~/boards/models/list';
import { mockBoardService } from '../mock_data';
describe('Board component', () => {
@@ -27,7 +26,6 @@ describe('Board component', () => {
disabled: false,
issueLinkBase: '/',
rootPath: '/',
- // eslint-disable-next-line no-undef
list: new List({
id: 1,
position: 0,
@@ -53,57 +51,62 @@ describe('Board component', () => {
expect(vm.$el.classList.contains('is-expandable')).toBe(true);
});
- it('board is expandable when list type is closed', done => {
- vm.list.type = 'closed';
-
- Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-expandable')).toBe(true);
-
- done();
- });
+ it('board is expandable when list type is closed', () => {
+ expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
});
- it('board is not expandable when list type is label', done => {
- vm.list.type = 'label';
- vm.list.isExpandable = false;
-
- Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-expandable')).toBe(false);
+ it('board is expandable when list type is label', () => {
+ expect(new List({ id: 1, list_type: 'closed' }).isExpandable).toBe(true);
+ });
- done();
- });
+ it('board is not expandable when list type is blank', () => {
+ expect(new List({ id: 1, list_type: 'blank' }).isExpandable).toBe(false);
});
- it('collapses when clicking header', done => {
+ it('does not collapse when clicking header', done => {
+ vm.list.isExpanded = true;
vm.$el.querySelector('.board-header').click();
Vue.nextTick(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
done();
});
});
- it('created sets isExpanded to true from localStorage', done => {
- vm.$el.querySelector('.board-header').click();
+ it('collapses when clicking the collapse icon', done => {
+ vm.list.isExpanded = true;
- return Vue.nextTick()
+ Vue.nextTick()
+ .then(() => {
+ vm.$el.querySelector('.board-title-caret').click();
+ })
.then(() => {
expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
+ done();
+ })
+ .catch(done.fail);
+ });
- // call created manually
- vm.$options.created[0].call(vm);
+ it('expands when clicking the expand icon', done => {
+ vm.list.isExpanded = false;
- return Vue.nextTick();
+ Vue.nextTick()
+ .then(() => {
+ vm.$el.querySelector('.board-title-caret').click();
})
.then(() => {
- expect(vm.$el.classList.contains('is-collapsed')).toBe(true);
-
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
done();
})
.catch(done.fail);
});
+ it('is expanded when created', () => {
+ expect(vm.list.isExpanded).toBe(true);
+ expect(vm.$el.classList.contains('is-collapsed')).toBe(false);
+ });
+
it('does render add issue button', () => {
expect(vm.$el.querySelector('.issue-count-badge-add-button')).not.toBeNull();
});
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index 002b5a005b8..f832096701f 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -14,7 +14,10 @@ describe('RepoEditor', () => {
let vm;
beforeEach(done => {
- const f = file();
+ const f = {
+ ...file(),
+ viewMode: 'editor',
+ };
const RepoEditor = Vue.extend(repoEditor);
vm = createComponentWithStore(RepoEditor, store, {
@@ -41,12 +44,17 @@ describe('RepoEditor', () => {
Editor.editorInstance.dispose();
});
- it('renders an ide container', done => {
- Vue.nextTick(() => {
- expect(vm.shouldHideEditor).toBeFalsy();
+ const findEditor = () => vm.$el.querySelector('.multi-file-editor-holder');
+ const changeRightPanelCollapsed = () => {
+ const { state } = vm.$store;
- done();
- });
+ state.rightPanelCollapsed = !state.rightPanelCollapsed;
+ };
+
+ it('renders an ide container', () => {
+ expect(vm.shouldHideEditor).toBeFalsy();
+ expect(vm.showEditor).toBe(true);
+ expect(findEditor()).not.toHaveCss({ display: 'none' });
});
it('renders only an edit tab', done => {
@@ -283,7 +291,7 @@ describe('RepoEditor', () => {
});
it('calls updateDimensions when rightPanelCollapsed is changed', done => {
- vm.$store.state.rightPanelCollapsed = true;
+ changeRightPanelCollapsed();
vm.$nextTick(() => {
expect(vm.editor.updateDimensions).toHaveBeenCalled();
@@ -358,6 +366,47 @@ describe('RepoEditor', () => {
});
});
+ describe('when files view mode is preview', () => {
+ beforeEach(done => {
+ spyOn(vm.editor, 'updateDimensions');
+ vm.file.viewMode = 'preview';
+ vm.$nextTick(done);
+ });
+
+ it('should hide editor', () => {
+ expect(vm.showEditor).toBe(false);
+ expect(findEditor()).toHaveCss({ display: 'none' });
+ });
+
+ it('should not update dimensions', done => {
+ changeRightPanelCollapsed();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.editor.updateDimensions).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('when file view mode changes to editor', () => {
+ beforeEach(done => {
+ vm.file.viewMode = 'editor';
+
+ // one tick to trigger watch
+ vm.$nextTick()
+ // another tick needed until we can update dimensions
+ .then(() => vm.$nextTick())
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('should update dimensions', () => {
+ expect(vm.editor.updateDimensions).toHaveBeenCalled();
+ });
+ });
+ });
+
it('calls removePendingTab when old file is pending', done => {
spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true);
spyOn(vm, 'removePendingTab');
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index ab8360193be..d3e10194d92 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -241,7 +241,7 @@ describe('Dashboard', () => {
Vue.nextTick()
.then(() => {
const dropdownItems = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item.is-active',
+ '.js-environments-dropdown .dropdown-item.active',
);
expect(dropdownItems.length).toEqual(1);
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index 8f3c493dd4c..c3ed079e33b 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -32,6 +32,26 @@ describe('Getters Notes Store', () => {
};
});
+ describe('showJumpToNextDiscussion', () => {
+ it('should return true if there are 2 or more unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsByDate: ['123', '456'],
+ allResolvableDiscussions: [],
+ };
+
+ expect(getters.showJumpToNextDiscussion(state, localGetters)()).toBe(true);
+ });
+
+ it('should return false if there are 1 or less unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsByDate: ['123'],
+ allResolvableDiscussions: [],
+ };
+
+ expect(getters.showJumpToNextDiscussion(state, localGetters)()).toBe(false);
+ });
+ });
+
describe('discussions', () => {
it('should return all discussions in the store', () => {
expect(getters.discussions(state)).toEqual([individualNote]);
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 78187b69563..daa898ca687 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -736,10 +736,9 @@ describe('Pipelines', () => {
});
describe('when a request is being made', () => {
- it('stops polling, cancels the request, fetches pipelines & restarts polling', done => {
+ it('stops polling, cancels the request, & restarts polling', done => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
- spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
spyOn(vm.service.cancelationSource, 'cancel').and.callThrough();
setTimeout(() => {
@@ -754,7 +753,6 @@ describe('Pipelines', () => {
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
- expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
@@ -765,10 +763,9 @@ describe('Pipelines', () => {
});
describe('when no request is being made', () => {
- it('stops polling, fetches pipelines & restarts polling', done => {
+ it('stops polling & restarts polling', done => {
spyOn(vm.poll, 'stop');
spyOn(vm.poll, 'restart');
- spyOn(vm, 'getPipelines').and.returnValue(Promise.resolve());
setTimeout(() => {
vm.$el.querySelector('.js-builds-dropdown-button').click();
@@ -776,7 +773,6 @@ describe('Pipelines', () => {
expect(vm.poll.stop).toHaveBeenCalled();
setTimeout(() => {
- expect(vm.getPipelines).toHaveBeenCalled();
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js
index 9ed4b04324a..55017b3e26b 100644
--- a/spec/javascripts/registry/components/collapsible_container_spec.js
+++ b/spec/javascripts/registry/components/collapsible_container_spec.js
@@ -72,21 +72,15 @@ describe('collapsible registry container', () => {
expect(findDeleteBtn()).not.toBeNull();
});
- describe('clicked on delete', () => {
- beforeEach(done => {
- findDeleteBtn().click();
- Vue.nextTick(done);
- });
-
- it('should open confirmation modal', () => {
- expect(vm.$el.querySelector('#confirm-repo-deletion-modal')).not.toBeNull();
- });
+ it('should call deleteItem when confirming deletion', done => {
+ findDeleteBtn().click();
+ spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
- it('should call deleteItem when confirming deletion', () => {
- spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
- vm.$el.querySelector('#confirm-repo-deletion-modal .btn-danger').click();
+ Vue.nextTick(() => {
+ document.querySelector('#confirm-repo-deletion-modal .btn-danger').click();
expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo);
+ done();
});
});
});
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
index d366c67a1b9..6a0b16f592e 100644
--- a/spec/javascripts/registry/components/table_registry_spec.js
+++ b/spec/javascripts/registry/components/table_registry_spec.js
@@ -46,23 +46,16 @@ describe('table registry', () => {
expect(findDeleteBtn()).toBeDefined();
});
- describe('clicked on delete', () => {
- beforeEach(done => {
- findDeleteBtn().click();
- Vue.nextTick(done);
- });
-
- it('should open confirmation modal and set itemToBeDeleted properly', () => {
- expect(vm.itemToBeDeleted).toEqual(firstImage);
- expect(vm.$el.querySelector('#confirm-image-deletion-modal')).not.toBeNull();
- });
+ it('should call deleteItem and reset itemToBeDeleted when confirming deletion', done => {
+ findDeleteBtn().click();
+ spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
- it('should call deleteItem and reset itemToBeDeleted when confirming deletion', () => {
- spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
- vm.$el.querySelector('#confirm-image-deletion-modal .btn-danger').click();
+ Vue.nextTick(() => {
+ document.querySelector('#confirm-image-deletion-modal .btn-danger').click();
expect(vm.deleteItem).toHaveBeenCalledWith(firstImage);
expect(vm.itemToBeDeleted).toBeNull();
+ done();
});
});
});
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 8c80a425581..2cc476ed52a 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -10,12 +10,16 @@ import VueResource from 'vue-resource';
import Translate from '~/vue_shared/translate';
import CheckEE from '~/vue_shared/mixins/is_ee';
import jasmineDiff from 'jasmine-diff';
+import { config as testUtilsConfig } from '@vue/test-utils';
import { getDefaultAdapter } from '~/lib/utils/axios_utils';
import { FIXTURES_PATH, TEST_HOST } from './test_constants';
import customMatchers from './matchers';
+// Tech debt issue TBD
+testUtilsConfig.logModifiedComponents = false;
+
const isHeadlessChrome = /\bHeadlessChrome\//.test(navigator.userAgent);
Vue.config.devtools = !isHeadlessChrome;
Vue.config.productionTip = false;
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 48f812f0db4..253413ae43e 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -218,7 +218,8 @@ export default {
'/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
email_patches_path: '/root/acets-app/merge_requests/22.patch',
plain_diff_path: '/root/acets-app/merge_requests/22.diff',
- status_path: '/root/acets-app/merge_requests/22.json',
+ merge_request_basic_path: '/root/acets-app/merge_requests/22.json?serializer=basic',
+ merge_request_widget_path: '/root/acets-app/merge_requests/22/widget.json',
merge_check_path: '/root/acets-app/merge_requests/22/merge_check',
ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status',
project_archived: false,
diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js
index 7da69e3fa84..6abcac5c0ff 100644
--- a/spec/javascripts/vue_shared/components/file_row_spec.js
+++ b/spec/javascripts/vue_shared/components/file_row_spec.js
@@ -90,6 +90,19 @@ describe('File row component', () => {
expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null);
});
+ it('is not rendered for `moved` entries in subfolders', () => {
+ createComponent({
+ file: {
+ path: 't5',
+ moved: true,
+ tree: [],
+ },
+ level: 2,
+ });
+
+ expect(vm.$el.nodeType).not.toEqual(1);
+ });
+
describe('new dropdown', () => {
beforeEach(() => {
createComponent({
diff --git a/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
new file mode 100644
index 00000000000..8d6bf45ab30
--- /dev/null
+++ b/spec/lib/gitlab/auth/ip_rate_limiter_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Auth::IpRateLimiter, :use_clean_rails_memory_store_caching do
+ let(:ip) { '10.2.2.3' }
+ let(:whitelist) { ['127.0.0.1'] }
+ let(:options) do
+ {
+ enabled: true,
+ ip_whitelist: whitelist,
+ bantime: 1.minute,
+ findtime: 1.minute,
+ maxretry: 2
+ }
+ end
+
+ subject { described_class.new(ip) }
+
+ before do
+ stub_rack_attack_setting(options)
+ end
+
+ after do
+ subject.reset!
+ end
+
+ describe '#register_fail!' do
+ it 'bans after 3 consecutive failures' do
+ expect(subject.banned?).to be_falsey
+
+ 3.times { subject.register_fail! }
+
+ expect(subject.banned?).to be_truthy
+ end
+
+ shared_examples 'whitelisted IPs' do
+ it 'does not ban after max retry limit' do
+ expect(subject.banned?).to be_falsey
+
+ 3.times { subject.register_fail! }
+
+ expect(subject.banned?).to be_falsey
+ end
+ end
+
+ context 'with a whitelisted netmask' do
+ before do
+ options[:ip_whitelist] = ['127.0.0.1', '10.2.2.0/24', 'bad']
+ stub_rack_attack_setting(options)
+ end
+
+ it_behaves_like 'whitelisted IPs'
+ end
+
+ context 'with a whitelisted IP' do
+ before do
+ options[:ip_whitelist] = ['10.2.2.3']
+ stub_rack_attack_setting(options)
+ end
+
+ it_behaves_like 'whitelisted IPs'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
index 51e16c99688..d88a2097ba2 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -17,15 +17,12 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'build has a deployment' do
- let!(:deployment) { create(:deployment, deployable: build) }
+ let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) }
+ let(:cluster) { nil }
context 'and a cluster to deploy to' do
let(:cluster) { create(:cluster, :group) }
- before do
- allow(build.deployment).to receive(:deployment_platform_cluster).and_return(cluster)
- end
-
it { is_expected.to be_truthy }
context 'and the cluster is not managed' do
@@ -48,28 +45,21 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'and no cluster to deploy to' do
- before do
- expect(deployment.deployment_platform_cluster).to be_nil
- end
-
it { is_expected.to be_falsey }
end
end
end
describe '#complete!' do
- let!(:deployment) { create(:deployment, deployable: build) }
+ let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) }
let(:service) { double(execute: true) }
+ let(:cluster) { nil }
subject { described_class.new(build).complete! }
context 'completion is required' do
let(:cluster) { create(:cluster, :group) }
- before do
- allow(build.deployment).to receive(:deployment_platform_cluster).and_return(cluster)
- end
-
it 'creates a kubernetes namespace' do
expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
.to receive(:new)
@@ -83,10 +73,6 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'completion is not required' do
- before do
- expect(deployment.deployment_platform_cluster).to be_nil
- end
-
it 'does not create a namespace' do
expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new)
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
new file mode 100644
index 00000000000..4d8edfeac80
--- /dev/null
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_batch_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Cleanup::OrphanJobArtifactFilesBatch do
+ let(:batch_size) { 10 }
+ let(:dry_run) { true }
+
+ subject(:batch) { described_class.new(batch_size: batch_size, dry_run: dry_run) }
+
+ context 'no dry run' do
+ let(:dry_run) { false }
+
+ it 'deletes only orphan job artifacts from disk' do
+ job_artifact = create(:ci_job_artifact, :archive)
+ orphan_artifact = create(:ci_job_artifact, :archive)
+ batch << artifact_path(job_artifact)
+ batch << artifact_path(orphan_artifact)
+ orphan_artifact.delete
+
+ batch.clean!
+
+ expect(batch.artifact_files.count).to eq(2)
+ expect(batch.lost_and_found.count).to eq(1)
+ expect(batch.lost_and_found.first.artifact_id).to eq(orphan_artifact.id)
+ end
+
+ it 'does not mix up job ID and artifact ID' do
+ # take maximum ID of both tables to avoid any collision
+ max_id = [Ci::Build.maximum(:id), Ci::JobArtifact.maximum(:id)].compact.max.to_i
+ job_a = create(:ci_build, id: max_id + 1)
+ job_b = create(:ci_build, id: max_id + 2)
+ # reuse the build IDs for the job artifact IDs, but swap them
+ job_artifact_b = create(:ci_job_artifact, :archive, job: job_b, id: max_id + 1)
+ job_artifact_a = create(:ci_job_artifact, :archive, job: job_a, id: max_id + 2)
+
+ batch << artifact_path(job_artifact_a)
+ batch << artifact_path(job_artifact_b)
+
+ job_artifact_b.delete
+
+ batch.clean!
+
+ expect(File.exist?(job_artifact_a.file.path)).to be_truthy
+ expect(File.exist?(job_artifact_b.file.path)).to be_falsey
+ end
+ end
+
+ context 'with dry run' do
+ it 'does not remove files' do
+ job_artifact = create(:ci_job_artifact, :archive)
+ batch << job_artifact.file.path
+ job_artifact.delete
+
+ expect(batch).not_to receive(:remove_file!)
+
+ batch.clean!
+
+ expect(File.exist?(job_artifact.file.path)).to be_truthy
+ end
+ end
+
+ def artifact_path(job_artifact)
+ Pathname.new(job_artifact.file.path).parent.to_s
+ end
+end
diff --git a/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
new file mode 100644
index 00000000000..974cc2c4660
--- /dev/null
+++ b/spec/lib/gitlab/cleanup/orphan_job_artifact_files_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Cleanup::OrphanJobArtifactFiles do
+ let(:null_logger) { Logger.new('/dev/null') }
+ subject(:cleanup) { described_class.new(logger: null_logger) }
+
+ before do
+ allow(null_logger).to receive(:info)
+ end
+
+ it 'passes on dry_run' do
+ expect(Gitlab::Cleanup::OrphanJobArtifactFilesBatch)
+ .to receive(:new)
+ .with(dry_run: false, batch_size: anything, logger: anything)
+ .at_least(:once)
+ .and_call_original
+
+ described_class.new(dry_run: false).run!
+ end
+
+ it 'errors when invalid niceness is given' do
+ cleanup = described_class.new(logger: null_logger, niceness: 'FooBar')
+
+ expect(null_logger).to receive(:error).with(/FooBar/)
+
+ cleanup.run!
+ end
+
+ it 'finds artifacts on disk' do
+ artifact = create(:ci_job_artifact, :archive)
+
+ expect(cleanup).to receive(:find_artifacts).and_yield(artifact.file.path)
+ cleanup.run!
+ end
+
+ it 'stops when limit is reached' do
+ cleanup = described_class.new(limit: 1)
+
+ mock_artifacts_found(cleanup, 'tmp/foo/bar/1', 'tmp/foo/bar/2')
+
+ cleanup.run!
+
+ expect(cleanup.total_found).to eq(1)
+ end
+
+ it 'cleans even if batch is not full' do
+ mock_artifacts_found(cleanup, 'tmp/foo/bar/1')
+
+ expect(cleanup).to receive(:clean_batch!).and_call_original
+ cleanup.run!
+ end
+
+ it 'cleans in batches' do
+ stub_const("#{described_class.name}::BATCH_SIZE", 2)
+ mock_artifacts_found(cleanup, 'tmp/foo/bar/1', 'tmp/foo/bar/2', 'tmp/foo/bar/3')
+
+ expect(cleanup).to receive(:clean_batch!).twice.and_call_original
+ cleanup.run!
+ end
+
+ def mock_artifacts_found(cleanup, *files)
+ mock = allow(cleanup).to receive(:find_artifacts)
+
+ files.each { |file| mock.and_yield(file) }
+ end
+end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 3cf3d032bf4..7409572288c 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -583,6 +583,24 @@ describe Gitlab::Database::MigrationHelpers do
model.add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
end
end
+
+ it 'adds a column with an array default value for a jsonb type' do
+ create(:project)
+ allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:transaction).and_yield
+ expect(model).to receive(:update_column_in_batches).with(:projects, :foo, '[{"foo":"json"}]').and_call_original
+
+ model.add_column_with_default(:projects, :foo, :jsonb, default: [{ foo: "json" }])
+ end
+
+ it 'adds a column with an object default value for a jsonb type' do
+ create(:project)
+ allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:transaction).and_yield
+ expect(model).to receive(:update_column_in_batches).with(:projects, :foo, '{"foo":"json"}').and_call_original
+
+ model.add_column_with_default(:projects, :foo, :jsonb, default: { foo: "json" })
+ end
end
context 'inside a transaction' do
diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
index 20842f55014..50138d272c4 100644
--- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
+++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb
@@ -67,7 +67,7 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do
end
describe '#authorize!' do
- it 'does not raise an error' do
+ it 'raises an error' do
expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
diff --git a/spec/lib/gitlab/graphql/copy_field_description_spec.rb b/spec/lib/gitlab/graphql/copy_field_description_spec.rb
new file mode 100644
index 00000000000..e7462c5b954
--- /dev/null
+++ b/spec/lib/gitlab/graphql/copy_field_description_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::CopyFieldDescription do
+ subject { Class.new.include(described_class) }
+
+ describe '.copy_field_description' do
+ let(:type) do
+ Class.new(Types::BaseObject) do
+ graphql_name "TestType"
+
+ field :field_name, GraphQL::STRING_TYPE, null: true, description: 'Foo'
+ end
+ end
+
+ it 'returns the correct description' do
+ expect(subject.copy_field_description(type, :field_name)).to eq('Foo')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
new file mode 100644
index 00000000000..91e90315b3e
--- /dev/null
+++ b/spec/lib/gitlab/graphql/find_argument_in_parent_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::FindArgumentInParent do
+ describe '#find' do
+ def build_node(parent = nil, args: {})
+ props = { irep_node: double(arguments: args) }
+ props[:parent] = parent if parent # The root node shouldn't respond to parent
+
+ double(props)
+ end
+
+ let(:parent) do
+ build_node(
+ build_node(
+ build_node(
+ build_node,
+ args: { myArg: 1 }
+ )
+ )
+ )
+ end
+ let(:arg_name) { :my_arg }
+
+ it 'searches parents and returns the argument' do
+ expect(described_class.find(parent, :my_arg)).to eq(1)
+ end
+
+ it 'can find argument when passed in as both Ruby and GraphQL-formatted symbols and strings' do
+ [:my_arg, :myArg, 'my_arg', 'myArg'].each do |arg|
+ expect(described_class.find(parent, arg)).to eq(1)
+ end
+ end
+
+ it 'returns nil if no arguments found in parents' do
+ expect(described_class.find(parent, :bar)).to eq(nil)
+ end
+
+ it 'can limit the depth it searches to' do
+ expect(described_class.find(parent, :my_arg, limit_depth: 1)).to eq(nil)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb
new file mode 100644
index 00000000000..927476cc655
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+describe Gitlab::Graphql::Loaders::PipelineForShaLoader do
+ include GraphqlHelpers
+
+ describe '#find_last' do
+ it 'batch-resolves latest pipeline' do
+ project = create(:project, :repository)
+ pipeline1 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
+ pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
+ pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
+
+ result = batch(max_queries: 1) do
+ [pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last }
+ end
+
+ expect(result).to contain_exactly(pipeline2, pipeline3)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 7a250603b6b..7baa52ffb4f 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -397,6 +397,7 @@ project:
- incident_management_setting
- merge_trains
- designs
+- project_aliases
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index 6512fe80a3b..8be074f4b9b 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -6760,7 +6760,7 @@
},
{
"id": 95,
- "title": "JIRA",
+ "title": "Jira",
"project_id": 5,
"created_at": "2016-06-14T15:01:51.255Z",
"updated_at": "2016-06-14T15:01:51.255Z",
diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb
index 642a6cb6caa..5bd76bc6081 100644
--- a/spec/lib/gitlab/issuable_sorter_spec.rb
+++ b/spec/lib/gitlab/issuable_sorter_spec.rb
@@ -26,7 +26,7 @@ describe Gitlab::IssuableSorter do
expect(described_class.sort(project1, unsorted)).to eq(sorted)
end
- context 'for JIRA issues' do
+ context 'for Jira issues' do
let(:sorted) do
[ExternalIssue.new('JIRA-1', project1),
ExternalIssue.new('JIRA-2', project1),
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index b0603d96eb2..da87df15746 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -52,13 +52,13 @@ describe Gitlab::Metrics::System do
end
describe '.cpu_time' do
- it 'returns a Fixnum' do
+ it 'returns a Float' do
expect(described_class.cpu_time).to be_an(Float)
end
end
describe '.real_time' do
- it 'returns a Fixnum' do
+ it 'returns a Float' do
expect(described_class.real_time).to be_an(Float)
end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index d982053d92e..7513dbeeb6f 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -197,14 +197,14 @@ describe Gitlab::ReferenceExtractor do
let(:issue) { create(:issue, project: project) }
context 'when GitLab issues are enabled' do
- it 'returns both JIRA and internal issues' do
+ it 'returns both Jira and internal issues' do
subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}")
expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
ExternalIssue.new('FOOBAR-4567', project),
issue]
end
- it 'returns only JIRA issues if the internal one does not exists' do
+ it 'returns only Jira issues if the internal one does not exists' do
subject.analyze("JIRA-123 and FOOBAR-4567 and #999")
expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
ExternalIssue.new('FOOBAR-4567', project)]
@@ -217,7 +217,7 @@ describe Gitlab::ReferenceExtractor do
project.save!
end
- it 'returns only JIRA issues' do
+ it 'returns only Jira issues' do
subject.analyze("JIRA-123 and FOOBAR-4567 and #{issue.to_reference}")
expect(subject.issues).to eq [ExternalIssue.new('JIRA-123', project),
ExternalIssue.new('FOOBAR-4567', project)]
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index fa1343fe759..56bbcc4c306 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -606,7 +606,7 @@ describe Notify do
it_behaves_like 'a user cannot unsubscribe through footer link'
it 'has the correct subject and body' do
- is_expected.to have_referable_subject(project_snippet, include_group: true, reply: true)
+ is_expected.to have_referable_subject(project_snippet, reply: true)
is_expected.to have_body_text project_snippet_note.note
end
end
@@ -813,7 +813,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_subject("Re: #{project.name} | #{project.group.name} | #{commit.title} (#{commit.short_id})")
+ is_expected.to have_subject("Re: #{project.name} | #{commit.title} (#{commit.short_id})")
is_expected.to have_body_text(commit.short_id)
end
end
@@ -839,7 +839,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_referable_subject(merge_request, include_group: true, reply: true)
+ is_expected.to have_referable_subject(merge_request, reply: true)
is_expected.to have_body_text note_on_merge_request_path
end
end
@@ -865,7 +865,7 @@ describe Notify do
it 'has the correct subject and body' do
aggregate_failures do
- is_expected.to have_referable_subject(issue, include_group: true, reply: true)
+ is_expected.to have_referable_subject(issue, reply: true)
is_expected.to have_body_text(note_on_issue_path)
end
end
@@ -931,7 +931,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_subject "Re: #{project.name} | #{project.group.name} | #{commit.title} (#{commit.short_id})"
+ is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})"
end
it 'contains a link to the commit' do
@@ -959,7 +959,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_referable_subject(merge_request, include_group: true, reply: true)
+ is_expected.to have_referable_subject(merge_request, reply: true)
end
it 'contains a link to the merge request note' do
@@ -987,7 +987,7 @@ describe Notify do
it_behaves_like 'appearance header and footer not enabled'
it 'has the correct subject' do
- is_expected.to have_referable_subject(issue, include_group: true, reply: true)
+ is_expected.to have_referable_subject(issue, reply: true)
end
it 'contains a link to the issue note' do
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 6ebc6337d50..55cea48b641 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1886,6 +1886,17 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.latest_for_shas' do
+ let(:sha) { 'abc' }
+
+ it 'returns latest pipeline for sha' do
+ create(:ci_pipeline, sha: sha)
+ pipeline2 = create(:ci_pipeline, sha: sha)
+
+ expect(described_class.latest_for_shas(sha)).to contain_exactly(pipeline2)
+ end
+ end
+
describe '.latest_successful_ids_per_project' do
let(:projects) { create_list(:project, 2) }
let!(:pipeline1) { create(:ci_pipeline, :success, project: projects[0]) }
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index 43fa1010b2b..3ff66a074e4 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -16,7 +16,7 @@ describe Clusters::Applications::Jupyter do
subject { jupyter.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#set_initial_status' do
diff --git a/spec/models/concerns/deployable_spec.rb b/spec/models/concerns/deployable_spec.rb
index 42bed9434f5..bb73dd8ade0 100644
--- a/spec/models/concerns/deployable_spec.rb
+++ b/spec/models/concerns/deployable_spec.rb
@@ -7,10 +7,6 @@ describe Deployable do
let(:deployment) { job.deployment }
let(:environment) { deployment&.environment }
- before do
- job.reload
- end
-
context 'when the deployable object will deploy to production' do
let!(:job) { create(:ci_build, :start_review_app) }
@@ -26,6 +22,16 @@ describe Deployable do
end
end
+ context 'when the deployable object will deploy to a cluster' do
+ let(:project) { create(:project) }
+ let!(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
+ let!(:job) { create(:ci_build, :start_review_app, project: project) }
+
+ it 'creates a deployment with cluster association' do
+ expect(deployment.cluster).to eq(cluster)
+ end
+ end
+
context 'when the deployable object will stop an environment' do
let!(:job) { create(:ci_build, :stop_review_app) }
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index f31e3e8821d..6034344d034 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -18,7 +18,7 @@ describe Mentionable do
let(:project) { create(:project) }
let(:mentionable) { Example.new }
- it 'excludes JIRA references' do
+ it 'excludes Jira references' do
allow(project).to receive_messages(jira_tracker?: true)
mentionable.project = project
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index a433878f3bc..8d0eb0f4a06 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -7,6 +7,7 @@ describe Deployment do
it { is_expected.to belong_to(:project).required }
it { is_expected.to belong_to(:environment).required }
+ it { is_expected.to belong_to(:cluster).class_name('Clusters::Cluster') }
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:deployable) }
@@ -294,6 +295,67 @@ describe Deployment do
end
end
+ describe '#has_metrics?' do
+ subject { deployment.has_metrics? }
+
+ context 'when deployment is failed' do
+ let(:deployment) { create(:deployment, :failed) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when deployment is success' do
+ let(:deployment) { create(:deployment, :success) }
+
+ context 'without a monitoring service' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with a Prometheus Service' do
+ let(:prometheus_service) { double(:prometheus_service, can_query?: true) }
+
+ before do
+ allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'with a Prometheus Service that cannot query' do
+ let(:prometheus_service) { double(:prometheus_service, can_query?: false) }
+
+ before do
+ allow(deployment.project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'with a cluster Prometheus' do
+ let(:deployment) { create(:deployment, :success, :on_cluster) }
+ let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: deployment.cluster) }
+
+ before do
+ expect(deployment.cluster.application_prometheus).to receive(:can_query?).and_return(true)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'fallback deployment platform' do
+ let(:cluster) { create(:cluster, :provided_by_user, environment_scope: '*', projects: [deployment.project]) }
+ let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
+
+ before do
+ expect(deployment.project).to receive(:deployment_platform).and_return(cluster.platform)
+ expect(cluster.application_prometheus).to receive(:can_query?).and_return(true)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
describe '#metrics' do
let(:deployment) { create(:deployment, :success) }
let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true) }
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 04ae9390436..fc08457a3c5 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -158,7 +158,7 @@ describe JiraService do
WebMock.stub_request(:post, @remote_link_url).with(basic_auth: %w(gitlab_jira_username gitlab_jira_password))
end
- it 'calls JIRA API' do
+ it 'calls Jira API' do
@jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project))
expect(WebMock).to have_requested(:post, @comment_url).with(
@@ -175,14 +175,14 @@ describe JiraService do
# Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links
# for more information
- it 'creates Remote Link reference in JIRA for comment' do
+ it 'creates Remote Link reference in Jira for comment' do
@jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project))
favicon_path = "http://localhost/assets/#{find_asset('favicon.png').digest_path}"
# Creates comment
expect(WebMock).to have_requested(:post, @comment_url)
- # Creates Remote Link in JIRA issue fields
+ # Creates Remote Link in Jira issue fields
expect(WebMock).to have_requested(:post, @remote_link_url).with(
body: hash_including(
GlobalID: 'GitLab',
@@ -319,7 +319,7 @@ describe JiraService do
end
context 'when the test succeeds' do
- it 'tries to get JIRA project with URL when API URL not set' do
+ it 'tries to get Jira project with URL when API URL not set' do
test_settings('jira.example.com')
end
@@ -327,7 +327,7 @@ describe JiraService do
expect(test_settings).to eq( { success: true, result: { 'url' => 'http://url' } })
end
- it 'tries to get JIRA project with API URL if set' do
+ it 'tries to get Jira project with API URL if set' do
jira_service.update(api_url: 'http://jira.api.com')
test_settings('jira.api.com')
end
@@ -462,7 +462,7 @@ describe JiraService do
end
it 'is initialized' do
- expect(@service.title).to eq('JIRA')
+ expect(@service.title).to eq('Jira')
expect(@service.description).to eq('Jira issue tracker')
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 0bd17dbacd7..13da7bd7407 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2298,48 +2298,6 @@ describe Repository do
end
end
- describe '#diverging_commit_counts' do
- let(:diverged_branch) { repository.find_branch('fix') }
- let(:root_ref_sha) { repository.raw_repository.commit(repository.root_ref).id }
- let(:diverged_branch_sha) { diverged_branch.dereferenced_target.sha }
-
- it 'returns the commit counts behind and ahead of default branch' do
- result = repository.diverging_commit_counts(diverged_branch)
-
- expect(result).to eq(behind: 29, ahead: 2)
- end
-
- context 'when gitaly_count_diverging_commits_no_max is enabled' do
- before do
- stub_feature_flags(gitaly_count_diverging_commits_no_max: true)
- end
-
- it 'calls diverging_commit_count without max count' do
- expect(repository.raw_repository)
- .to receive(:diverging_commit_count)
- .with(root_ref_sha, diverged_branch_sha)
- .and_return([29, 2])
-
- repository.diverging_commit_counts(diverged_branch)
- end
- end
-
- context 'when gitaly_count_diverging_commits_no_max is disabled' do
- before do
- stub_feature_flags(gitaly_count_diverging_commits_no_max: false)
- end
-
- it 'calls diverging_commit_count with max count' do
- expect(repository.raw_repository)
- .to receive(:diverging_commit_count)
- .with(root_ref_sha, diverged_branch_sha, max_count: Repository::MAX_DIVERGING_COUNT)
- .and_return([29, 2])
-
- repository.diverging_commit_counts(diverged_branch)
- end
- end
- end
-
describe '#refresh_method_caches' do
it 'refreshes the caches of the given types' do
expect(repository).to receive(:expire_method_caches)
diff --git a/spec/policies/award_emoji_policy_spec.rb b/spec/policies/award_emoji_policy_spec.rb
new file mode 100644
index 00000000000..2e3693c58d7
--- /dev/null
+++ b/spec/policies/award_emoji_policy_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojiPolicy do
+ let(:user) { create(:user) }
+ let(:award_emoji) { create(:award_emoji, awardable: awardable) }
+
+ subject { described_class.new(user, award_emoji) }
+
+ shared_examples 'when the user can read the awardable' do
+ context do
+ let(:project) { create(:project, :public) }
+
+ it { expect_allowed(:read_emoji) }
+ end
+ end
+
+ shared_examples 'when the user cannot read the awardable' do
+ context do
+ let(:project) { create(:project, :private) }
+
+ it { expect_disallowed(:read_emoji) }
+ end
+ end
+
+ context 'when the awardable is an issue' do
+ let(:awardable) { create(:issue, project: project) }
+
+ include_examples 'when the user can read the awardable'
+ include_examples 'when the user cannot read the awardable'
+ end
+
+ context 'when the awardable is a merge request' do
+ let(:awardable) { create(:merge_request, source_project: project) }
+
+ include_examples 'when the user can read the awardable'
+ include_examples 'when the user cannot read the awardable'
+ end
+
+ context 'when the awardable is a note' do
+ let(:awardable) { create(:note_on_merge_request, project: project) }
+
+ include_examples 'when the user can read the awardable'
+ include_examples 'when the user cannot read the awardable'
+ end
+
+ context 'when the awardable is a snippet' do
+ let(:awardable) { create(:project_snippet, :public, project: project) }
+
+ include_examples 'when the user can read the awardable'
+ include_examples 'when the user cannot read the awardable'
+ end
+end
diff --git a/spec/presenters/award_emoji_presenter_spec.rb b/spec/presenters/award_emoji_presenter_spec.rb
new file mode 100644
index 00000000000..e2ada2a3c93
--- /dev/null
+++ b/spec/presenters/award_emoji_presenter_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojiPresenter do
+ let(:emoji_name) { 'thumbsup' }
+ let(:award_emoji) { build(:award_emoji, name: emoji_name) }
+ let(:presenter) { described_class.new(award_emoji) }
+
+ describe '#description' do
+ it { expect(presenter.description).to eq Gitlab::Emoji.emojis[emoji_name]['description'] }
+ end
+
+ describe '#unicode' do
+ it { expect(presenter.unicode).to eq Gitlab::Emoji.emojis[emoji_name]['unicode'] }
+ end
+
+ describe '#unicode_version' do
+ it { expect(presenter.unicode_version).to eq Gitlab::Emoji.emoji_unicode_version(emoji_name) }
+ end
+
+ describe '#emoji' do
+ it { expect(presenter.emoji).to eq Gitlab::Emoji.emojis[emoji_name]['moji'] }
+ end
+
+ describe 'when presenting an award emoji with an invalid name' do
+ let(:emoji_name) { 'invalid-name' }
+
+ it 'returns nil for all properties' do
+ expect(presenter.description).to be_nil
+ expect(presenter.emoji).to be_nil
+ expect(presenter.unicode).to be_nil
+ expect(presenter.unicode_version).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
new file mode 100644
index 00000000000..3982125a38a
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding an AwardEmoji' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:awardable) { create(:note) }
+ let(:project) { awardable.project }
+ let(:emoji_name) { 'thumbsup' }
+ let(:mutation) do
+ variables = {
+ awardable_id: GitlabSchema.id_from_object(awardable).to_s,
+ name: emoji_name
+ }
+
+ graphql_mutation(:add_award_emoji, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:add_award_emoji)
+ end
+
+ shared_examples 'a mutation that does not create an AwardEmoji' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { AwardEmoji.count }
+ end
+ end
+
+ context 'when the user does not have permission' do
+ it_behaves_like 'a mutation that does not create an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ end
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ context 'when the given awardable is not an Awardable' do
+ let(:awardable) { create(:label) }
+
+ it_behaves_like 'a mutation that does not create an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable is an Awardable but still cannot be awarded an emoji' do
+ let(:awardable) { create(:system_note) }
+
+ it_behaves_like 'a mutation that does not create an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable an Awardable' do
+ it 'creates an emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(1)
+ end
+
+ it 'returns the emoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['awardEmoji']['name']).to eq(emoji_name)
+ end
+
+ context 'when there were active record validation errors' do
+ before do
+ expect_next_instance_of(AwardEmoji) do |award|
+ expect(award).to receive(:valid?).at_least(:once).and_return(false)
+ expect(award).to receive_message_chain(
+ :errors,
+ :full_messages
+ ).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it_behaves_like 'a mutation that does not create an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2']
+
+ it 'returns an empty awardEmoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('awardEmoji')
+ expect(mutation_response['awardEmoji']).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
new file mode 100644
index 00000000000..c78f0c7ca27
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Removing an AwardEmoji' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:awardable) { create(:note) }
+ let(:project) { awardable.project }
+ let(:emoji_name) { 'thumbsup' }
+ let(:input) { { awardable_id: GitlabSchema.id_from_object(awardable).to_s, name: emoji_name } }
+
+ let(:mutation) do
+ graphql_mutation(:remove_award_emoji, input)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:remove_award_emoji)
+ end
+
+ def create_award_emoji(user)
+ create(:award_emoji, name: emoji_name, awardable: awardable, user: user )
+ end
+
+ shared_examples 'a mutation that does not destroy an AwardEmoji' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { AwardEmoji.count }
+ end
+ end
+
+ shared_examples 'a mutation that does not authorize the user' do
+ it_behaves_like 'a mutation that does not destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ end
+
+ context 'when the current_user does not own the award emoji' do
+ let!(:award_emoji) { create_award_emoji(create(:user)) }
+
+ it_behaves_like 'a mutation that does not authorize the user'
+ end
+
+ context 'when the current_user owns the award emoji' do
+ let!(:award_emoji) { create_award_emoji(current_user) }
+
+ context 'when the given awardable is not an Awardable' do
+ let(:awardable) { create(:label) }
+
+ it_behaves_like 'a mutation that does not destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable is an Awardable' do
+ it 'removes the emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(-1)
+ end
+
+ it 'returns no errors' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).to be_nil
+ end
+
+ it 'returns an empty awardEmoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('awardEmoji')
+ expect(mutation_response['awardEmoji']).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
new file mode 100644
index 00000000000..31145730f10
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Toggling an AwardEmoji' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user) }
+ let(:awardable) { create(:note) }
+ let(:project) { awardable.project }
+ let(:emoji_name) { 'thumbsup' }
+ let(:mutation) do
+ variables = {
+ awardable_id: GitlabSchema.id_from_object(awardable).to_s,
+ name: emoji_name
+ }
+
+ graphql_mutation(:toggle_award_emoji, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:toggle_award_emoji)
+ end
+
+ shared_examples 'a mutation that does not create or destroy an AwardEmoji' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { AwardEmoji.count }
+ end
+ end
+
+ def create_award_emoji(user)
+ create(:award_emoji, name: emoji_name, awardable: awardable, user: user )
+ end
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ context 'when the given awardable is not an Awardable' do
+ let(:awardable) { create(:label) }
+
+ it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable is an Awardable but still cannot be awarded an emoji' do
+ let(:awardable) { create(:system_note) }
+
+ it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Cannot award emoji to this resource']
+ end
+
+ context 'when the given awardable is an Awardable' do
+ context 'when no emoji has been awarded by the current_user yet' do
+ # Create an award emoji for another user. This therefore tests that
+ # toggling is correctly scoped to the user's emoji only.
+ let!(:award_emoji) { create_award_emoji(create(:user)) }
+
+ it 'creates an emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(1)
+ end
+
+ it 'returns the emoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['awardEmoji']['name']).to eq(emoji_name)
+ end
+
+ it 'returns toggledOn as true' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['toggledOn']).to eq(true)
+ end
+
+ context 'when there were active record validation errors' do
+ before do
+ expect_next_instance_of(AwardEmoji) do |award|
+ expect(award).to receive(:valid?).at_least(:once).and_return(false)
+ expect(award).to receive_message_chain(:errors, :full_messages).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2']
+
+ it 'returns an empty awardEmoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('awardEmoji')
+ expect(mutation_response['awardEmoji']).to be_nil
+ end
+ end
+ end
+
+ context 'when an emoji has been awarded by the current_user' do
+ let!(:award_emoji) { create_award_emoji(current_user) }
+
+ it 'removes the emoji' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { AwardEmoji.count }.by(-1)
+ end
+
+ it 'returns no errors' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors).to be_nil
+ end
+
+ it 'returns an empty awardEmoji' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('awardEmoji')
+ expect(mutation_response['awardEmoji']).to be_nil
+ end
+
+ it 'returns toggledOn as false' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['toggledOn']).to eq(false)
+ end
+ end
+ end
+ end
+
+ context 'when the user does not have permission' do
+ it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ end
+end
diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb
index b07aa1e12d3..94128cc21ee 100644
--- a/spec/requests/api/graphql/project/tree/tree_spec.rb
+++ b/spec/requests/api/graphql/project/tree/tree_spec.rb
@@ -33,6 +33,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([])
expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([])
end
+
+ it 'returns null commit' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']['last_commit']).to be_nil
+ end
end
context 'when ref does not exist' do
@@ -45,6 +51,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['submodules']['edges']).to eq([])
expect(graphql_data['project']['repository']['tree']['blobs']['edges']).to eq([])
end
+
+ it 'returns null commit' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']['last_commit']).to be_nil
+ end
end
context 'when ref and path exist' do
@@ -61,6 +73,12 @@ describe 'getting a tree in a project' do
expect(graphql_data['project']['repository']['tree']['blobs']['edges'].size).to be > 0
expect(graphql_data['project']['repository']['tree']['submodules']['edges'].size).to be > 0
end
+
+ it 'returns tree latest commit' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data['project']['repository']['tree']['lastCommit']).to be_present
+ end
end
context 'when current user is nil' do
diff --git a/spec/services/branches/diverging_commit_counts_service_spec.rb b/spec/services/branches/diverging_commit_counts_service_spec.rb
new file mode 100644
index 00000000000..bfdbebdb7c1
--- /dev/null
+++ b/spec/services/branches/diverging_commit_counts_service_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Branches::DivergingCommitCountsService do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ describe '#call' do
+ let(:diverged_branch) { repository.find_branch('fix') }
+ let(:root_ref_sha) { repository.raw_repository.commit(repository.root_ref).id }
+ let(:diverged_branch_sha) { diverged_branch.dereferenced_target.sha }
+
+ let(:service) { described_class.new(repository) }
+
+ it 'returns the commit counts behind and ahead of default branch' do
+ result = service.call(diverged_branch)
+
+ expect(result).to eq(behind: 29, ahead: 2)
+ end
+
+ context 'when gitaly_count_diverging_commits_no_max is enabled' do
+ before do
+ stub_feature_flags(gitaly_count_diverging_commits_no_max: true)
+ end
+
+ it 'calls diverging_commit_count without max count' do
+ expect(repository.raw_repository)
+ .to receive(:diverging_commit_count)
+ .with(root_ref_sha, diverged_branch_sha)
+ .and_return([29, 2])
+
+ service.call(diverged_branch)
+ end
+ end
+
+ context 'when gitaly_count_diverging_commits_no_max is disabled' do
+ before do
+ stub_feature_flags(gitaly_count_diverging_commits_no_max: false)
+ end
+
+ it 'calls diverging_commit_count with max count' do
+ expect(repository.raw_repository)
+ .to receive(:diverging_commit_count)
+ .with(root_ref_sha, diverged_branch_sha, max_count: Repository::MAX_DIVERGING_COUNT)
+ .and_return([29, 2])
+
+ service.call(diverged_branch)
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index a0ac7dba89d..0e0da6a13ab 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe MergeRequests::CreateFromIssueService do
+ include ProjectForksHelper
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:label_ids) { create_pair(:label, project: project).map(&:id) }
@@ -10,139 +12,174 @@ describe MergeRequests::CreateFromIssueService do
let(:issue) { create(:issue, project: project, milestone_id: milestone_id) }
let(:custom_source_branch) { 'custom-source-branch' }
- subject(:service) { described_class.new(project, user, issue_iid: issue.iid) }
- subject(:service_with_custom_source_branch) { described_class.new(project, user, issue_iid: issue.iid, branch_name: custom_source_branch) }
+ subject(:service) { described_class.new(project, user, service_params) }
+ subject(:service_with_custom_source_branch) { described_class.new(project, user, branch_name: custom_source_branch, **service_params) }
before do
project.add_developer(user)
end
describe '#execute' do
- it 'returns an error with invalid issue iid' do
- result = described_class.new(project, user, issue_iid: -1).execute
+ shared_examples_for 'a service that creates a merge request from an issue' do
+ it 'returns an error when user can not create merge request on target project' do
+ result = described_class.new(project, create(:user), service_params).execute
- expect(result[:status]).to eq(:error)
- expect(result[:message]).to eq('Invalid issue iid')
- end
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Not allowed to create merge request')
+ end
- it 'delegates issue search to IssuesFinder' do
- expect_any_instance_of(IssuesFinder).to receive(:find_by).once.and_call_original
+ it 'returns an error with invalid issue iid' do
+ result = described_class.new(project, user, issue_iid: -1).execute
- described_class.new(project, user, issue_iid: -1).execute
- end
-
- it "inherits labels" do
- issue.assign_attributes(label_ids: label_ids)
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Invalid issue iid')
+ end
- result = service.execute
+ it 'creates a branch based on issue title' do
+ service.execute
- expect(result[:merge_request].label_ids).to eq(label_ids)
- end
+ expect(target_project.repository.branch_exists?(issue.to_branch_name)).to be_truthy
+ end
- it "inherits milestones" do
- result = service.execute
+ it 'creates a branch using passed name' do
+ service_with_custom_source_branch.execute
- expect(result[:merge_request].milestone_id).to eq(milestone_id)
- end
+ expect(target_project.repository.branch_exists?(custom_source_branch)).to be_truthy
+ end
- it 'delegates the branch creation to CreateBranchService' do
- expect_any_instance_of(CreateBranchService).to receive(:execute).once.and_call_original
+ it 'creates the new_merge_request system note' do
+ expect(SystemNoteService).to receive(:new_merge_request).with(issue, project, user, instance_of(MergeRequest))
- service.execute
- end
+ service.execute
+ end
- it 'creates a branch based on issue title' do
- service.execute
+ it 'creates the new_issue_branch system note when the branch could be created but the merge_request cannot be created' do
+ expect_any_instance_of(MergeRequest).to receive(:valid?).at_least(:once).and_return(false)
- expect(project.repository.branch_exists?(issue.to_branch_name)).to be_truthy
- end
+ expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, issue.to_branch_name, branch_project: target_project)
- it 'creates a branch using passed name' do
- service_with_custom_source_branch.execute
+ service.execute
+ end
- expect(project.repository.branch_exists?(custom_source_branch)).to be_truthy
- end
+ it 'creates a merge request' do
+ expect { service.execute }.to change(target_project.merge_requests, :count).by(1)
+ end
- it 'creates the new_merge_request system note' do
- expect(SystemNoteService).to receive(:new_merge_request).with(issue, project, user, instance_of(MergeRequest))
+ it 'sets the merge request author to current user' do
+ result = service.execute
- service.execute
- end
+ expect(result[:merge_request].author).to eq(user)
+ end
- it 'creates the new_issue_branch system note when the branch could be created but the merge_request cannot be created' do
- project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED)
+ it 'sets the merge request source branch to the new issue branch' do
+ result = service.execute
- expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, issue.to_branch_name)
+ expect(result[:merge_request].source_branch).to eq(issue.to_branch_name)
+ end
- service.execute
- end
+ it 'sets the merge request source branch to the passed branch name' do
+ result = service_with_custom_source_branch.execute
- it 'creates a merge request' do
- expect { service.execute }.to change(project.merge_requests, :count).by(1)
- end
+ expect(result[:merge_request].source_branch).to eq(custom_source_branch)
+ end
- it 'sets the merge request title to: "WIP: Resolves "$issue-title"' do
- result = service.execute
+ it 'sets the merge request target branch to the project default branch' do
+ result = service.execute
- expect(result[:merge_request].title).to eq("WIP: Resolve \"#{issue.title}\"")
- end
+ expect(result[:merge_request].target_branch).to eq(target_project.default_branch)
+ end
- it 'sets the merge request author to current user' do
- result = service.execute
+ it 'executes quick actions if the build service sets them in the description' do
+ allow(service).to receive(:merge_request).and_wrap_original do |m, *args|
+ m.call(*args).tap do |merge_request|
+ merge_request.description = "/assign #{user.to_reference}"
+ end
+ end
- expect(result[:merge_request].author).to eq(user)
- end
+ result = service.execute
- it 'sets the merge request source branch to the new issue branch' do
- result = service.execute
+ expect(result[:merge_request].assignees).to eq([user])
+ end
- expect(result[:merge_request].source_branch).to eq(issue.to_branch_name)
- end
+ context 'when ref branch is set' do
+ subject { described_class.new(project, user, ref: 'feature', **service_params).execute }
- it 'sets the merge request source branch to the passed branch name' do
- result = service_with_custom_source_branch.execute
+ it 'sets the merge request source branch to the new issue branch' do
+ expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
+ end
- expect(result[:merge_request].source_branch).to eq(custom_source_branch)
- end
+ it 'sets the merge request target branch to the ref branch' do
+ expect(subject[:merge_request].target_branch).to eq('feature')
+ end
- it 'sets the merge request target branch to the project default branch' do
- result = service.execute
+ context 'when ref branch does not exist' do
+ subject { described_class.new(project, user, ref: 'no-such-branch', **service_params).execute }
- expect(result[:merge_request].target_branch).to eq(project.default_branch)
- end
+ it 'creates a merge request' do
+ expect { subject }.to change(target_project.merge_requests, :count).by(1)
+ end
- it 'executes quick actions if the build service sets them in the description' do
- allow(service).to receive(:merge_request).and_wrap_original do |m, *args|
- m.call(*args).tap do |merge_request|
- merge_request.description = "/assign #{user.to_reference}"
+ it 'sets the merge request target branch to the project default branch' do
+ expect(subject[:merge_request].target_branch).to eq(target_project.default_branch)
+ end
end
end
+ end
- result = service.execute
+ context 'no target_project_id specified' do
+ let(:service_params) { { issue_iid: issue.iid } }
+ let(:target_project) { project }
- expect(result[:merge_request].assignees).to eq([user])
- end
+ it_behaves_like 'a service that creates a merge request from an issue'
- context 'when ref branch is set' do
- subject { described_class.new(project, user, issue_iid: issue.iid, ref: 'feature').execute }
+ it "inherits labels" do
+ issue.assign_attributes(label_ids: label_ids)
- it 'sets the merge request source branch to the new issue branch' do
- expect(subject[:merge_request].source_branch).to eq(issue.to_branch_name)
+ result = service.execute
+
+ expect(result[:merge_request].label_ids).to eq(label_ids)
end
- it 'sets the merge request target branch to the ref branch' do
- expect(subject[:merge_request].target_branch).to eq('feature')
+ it "inherits milestones" do
+ result = service.execute
+
+ expect(result[:merge_request].milestone_id).to eq(milestone_id)
end
- context 'when ref branch does not exist' do
- subject { described_class.new(project, user, issue_iid: issue.iid, ref: 'no-such-branch').execute }
+ it 'sets the merge request title to: "WIP: Resolves "$issue-title"' do
+ result = service.execute
- it 'creates a merge request' do
- expect { subject }.to change(project.merge_requests, :count).by(1)
+ expect(result[:merge_request].title).to eq("WIP: Resolve \"#{issue.title}\"")
+ end
+ end
+
+ context 'target_project_id is specified' do
+ let(:service_params) { { issue_iid: issue.iid, target_project_id: target_project.id } }
+
+ context 'target project is not a fork of the project' do
+ let(:target_project) { create(:project, :repository) }
+
+ it 'returns an error about not finding the project' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Project not found')
end
- it 'sets the merge request target branch to the project default branch' do
- expect(subject[:merge_request].target_branch).to eq(project.default_branch)
+ it 'does not create merge request' do
+ expect { service.execute }.to change(target_project.merge_requests, :count).by(0)
+ end
+ end
+
+ context 'target project is a fork of project project' do
+ let(:target_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'a service that creates a merge request from an issue'
+
+ it 'sets the merge request title to: "WIP: $issue-branch-name' do
+ result = service.execute
+
+ expect(result[:merge_request].title).to eq("WIP: #{issue.to_branch_name.titleize.humanize}")
end
end
end
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index 2fbe5468b21..aa759ac9edc 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -58,7 +58,7 @@ describe MergeRequests::MergeService do
expect(issue.reload.closed?).to be_truthy
end
- context 'with JIRA integration' do
+ context 'with Jira integration' do
include JiraServiceHelper
let(:jira_tracker) { project.create_jira_service }
@@ -72,7 +72,7 @@ describe MergeRequests::MergeService do
allow(merge_request).to receive(:commits).and_return([commit])
end
- it 'closes issues on JIRA issue tracker' do
+ it 'closes issues on Jira issue tracker' do
jira_issue = ExternalIssue.new('JIRA-123', project)
stub_jira_urls(jira_issue)
commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
@@ -98,7 +98,7 @@ describe MergeRequests::MergeService do
end
context "wrong issue markdown" do
- it 'does not close issues on JIRA issue tracker' do
+ it 'does not close issues on Jira issue tracker' do
jira_issue = ExternalIssue.new('#JIRA-123', project)
stub_jira_urls(jira_issue)
commit = double('commit', safe_message: "Fixes #{jira_issue.to_reference}")
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 30a867fa7ba..97377c2f560 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -454,16 +454,32 @@ describe SystemNoteService do
end
describe '.new_issue_branch' do
- subject { described_class.new_issue_branch(noteable, project, author, "1-mepmep") }
+ let(:branch) { '1-mepmep' }
- it_behaves_like 'a system note' do
- let(:action) { 'branch' }
- end
+ subject { described_class.new_issue_branch(noteable, project, author, branch, branch_project: branch_project) }
- context 'when a branch is created from the new branch button' do
- it 'sets the note text' do
- expect(subject.note).to start_with("created branch [`1-mepmep`]")
+ shared_examples_for 'a system note for new issue branch' do
+ it_behaves_like 'a system note' do
+ let(:action) { 'branch' }
end
+
+ context 'when a branch is created from the new branch button' do
+ it 'sets the note text' do
+ expect(subject.note).to start_with("created branch [`#{branch}`]")
+ end
+ end
+ end
+
+ context 'branch_project is set' do
+ let(:branch_project) { create(:project, :repository) }
+
+ it_behaves_like 'a system note for new issue branch'
+ end
+
+ context 'branch_project is not set' do
+ let(:branch_project) { nil }
+
+ it_behaves_like 'a system note for new issue branch'
end
end
@@ -477,7 +493,7 @@ describe SystemNoteService do
end
it 'sets the new merge request note text' do
- expect(subject.note).to eq("created merge request #{merge_request.to_reference} to address this issue")
+ expect(subject.note).to eq("created merge request #{merge_request.to_reference(project)} to address this issue")
end
end
@@ -750,7 +766,7 @@ describe SystemNoteService do
end
end
- describe 'JIRA integration' do
+ describe 'Jira integration' do
include JiraServiceHelper
let(:project) { create(:jira_project, :repository) }
diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb
index 592962ebf7c..3abb5096a7a 100644
--- a/spec/support/api/boards_shared_examples.rb
+++ b/spec/support/api/boards_shared_examples.rb
@@ -14,6 +14,16 @@ shared_examples_for 'group and project boards' do |route_definition, ee = false|
end
end
+ it 'avoids N+1 queries' do
+ pat = create(:personal_access_token, user: user)
+ control = ActiveRecord::QueryRecorder.new { get api(root_url, personal_access_token: pat) }
+
+ create(:milestone, "#{board_parent.class.name.underscore}": board_parent)
+ create(:board, "#{board_parent.class.name.underscore}": board_parent)
+
+ expect { get api(root_url, personal_access_token: pat) }.not_to exceed_query_limit(control)
+ end
+
describe "GET #{route_definition}" do
context "when unauthenticated" do
it "returns authentication error" do
diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb
index a7175491fa0..ed049daba80 100644
--- a/spec/support/helpers/email_helpers.rb
+++ b/spec/support/helpers/email_helpers.rb
@@ -37,19 +37,8 @@ module EmailHelpers
ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) }
end
- def have_referable_subject(referable, include_project: true, include_group: false, reply: false)
- context = []
-
- context << referable.project.name if include_project && referable.project
- context << referable.project.group.name if include_group && referable.project.group
-
- prefix =
- if context.any?
- context.join(' | ') + ' | '
- else
- ''
- end
-
+ def have_referable_subject(referable, include_project: true, reply: false)
+ prefix = (include_project && referable.project ? "#{referable.project.name} | " : '').freeze
prefix = "Re: #{prefix}" if reply
suffix = "#{referable.title} (#{referable.to_reference})"
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index bcf6669f37d..1a09d48f4cd 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -4,10 +4,7 @@ module GraphqlHelpers
# makes an underscored string look like a fieldname
# "merge_request" => "mergeRequest"
def self.fieldnamerize(underscored_field_name)
- graphql_field_name = underscored_field_name.to_s.camelize
- graphql_field_name[0] = graphql_field_name[0].downcase
-
- graphql_field_name
+ underscored_field_name.to_s.camelize(:lower)
end
# Run a loader's named resolver
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 477bbf1c2e0..7e955f3d593 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -4,7 +4,7 @@ module JiraServiceHelper
def jira_service_settings
properties = {
- title: "JIRA tracker",
+ title: "Jira tracker",
url: JIRA_URL,
username: 'jira-user',
password: 'my-secret-password',
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 0d591f038ce..c372a3f0e49 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -95,6 +95,11 @@ module StubConfiguration
allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages))
end
+ def stub_rack_attack_setting(messages)
+ allow(Gitlab.config.rack_attack).to receive(:git_basic_auth).and_return(messages)
+ allow(Gitlab.config.rack_attack.git_basic_auth).to receive_messages(to_settings(messages))
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index f985b2dcbba..ab0550e2613 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -270,7 +270,7 @@ shared_examples_for 'common trace features' do
include ExclusiveLeaseHelpers
before do
- stub_exclusive_lease_taken("trace:write:lock:#{trace.job.id}", timeout: 1.minute)
+ stub_exclusive_lease_taken("trace:write:lock:#{trace.job.id}", timeout: 10.minutes)
end
it 'blocks concurrent archiving' do
diff --git a/spec/support/shared_examples/graphql/mutation_shared_examples.rb b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
new file mode 100644
index 00000000000..022d41c0bdd
--- /dev/null
+++ b/spec/support/shared_examples/graphql/mutation_shared_examples.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# Shared example for expecting top-level errors.
+# See https://graphql-ruby.org/mutations/mutation_errors#raising-errors
+#
+# { errors: [] }
+#
+# There must be a method or let called `mutation` defined that executes
+# the mutation.
+RSpec.shared_examples 'a mutation that returns top-level errors' do |errors:|
+ it do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ error_messages = graphql_errors.map { |e| e['message'] }
+
+ expect(error_messages).to eq(errors)
+ end
+end
+
+# Shared example for expecting schema-level errors.
+# See https://graphql-ruby.org/mutations/mutation_errors#errors-as-data
+#
+# { data: { mutationName: { errors: [] } } }
+#
+# There must be:
+# - a method or let called `mutation` defined that executes the mutation
+# - a `mutation_response` method defined that returns the data of the mutation response.
+RSpec.shared_examples 'a mutation that returns errors in the response' do |errors:|
+ it do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['errors']).to eq(errors)
+ end
+end
diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb
index 9dbd1d8e867..5359831f8f8 100644
--- a/spec/support/shared_examples/services/boards/issues_move_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_move_service.rb
@@ -1,8 +1,17 @@
shared_examples 'issues move service' do |group|
+ shared_examples 'updating timestamps' do
+ it 'updates updated_at' do
+ expect {described_class.new(parent, user, params).execute(issue)}
+ .to change {issue.reload.updated_at}
+ end
+ end
+
context 'when moving an issue between lists' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
+ it_behaves_like 'updating timestamps'
+
it 'delegates the label changes to Issues::UpdateService' do
service = double(:service)
expect(Issues::UpdateService).to receive(:new).and_return(service)
@@ -24,6 +33,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: closed.id } }
+ it_behaves_like 'updating timestamps'
+
it 'delegates the close proceedings to Issues::CloseService' do
expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once
@@ -46,6 +57,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression], milestone: milestone) }
let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: backlog.id } }
+ it_behaves_like 'updating timestamps'
+
it 'keeps labels and milestone' do
described_class.new(parent, user, params).execute(issue)
issue.reload
@@ -59,6 +72,8 @@ shared_examples 'issues move service' do |group|
let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) }
let(:params) { { board_id: board1.id, from_list_id: closed.id, to_list_id: list2.id } }
+ it_behaves_like 'updating timestamps'
+
it 'delegates the re-open proceedings to Issues::ReopenService' do
expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once
@@ -75,10 +90,13 @@ shared_examples 'issues move service' do |group|
end
context 'when moving to same list' do
- let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
- let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
- let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
- let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
+ let(:assignee) { create(:user) }
+ let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
+ let(:issue1) { create(:labeled_issue, project: project, labels: [bug, development]) }
+ let(:issue2) { create(:labeled_issue, project: project, labels: [bug, development]) }
+ let(:issue) do
+ create(:labeled_issue, project: project, labels: [bug, development], assignees: [assignee])
+ end
it 'returns false' do
expect(described_class.new(parent, user, params).execute(issue)).to eq false
@@ -90,18 +108,36 @@ shared_examples 'issues move service' do |group|
expect(issue.reload.labels).to contain_exactly(bug, development)
end
- it 'sorts issues' do
- [issue, issue1, issue2].each do |issue|
- issue.move_to_end && issue.save!
- end
+ it 'keeps issues assignees' do
+ described_class.new(parent, user, params).execute(issue)
+
+ expect(issue.reload.assignees).to contain_exactly(assignee)
+ end
- params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
+ it 'sorts issues' do
+ reorder_issues(params, issues: [issue, issue1, issue2])
described_class.new(parent, user, params).execute(issue)
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
+ it 'does not update updated_at' do
+ reorder_issues(params, issues: [issue, issue1, issue2])
+
+ updated_at = issue.updated_at
+ updated_at1 = issue1.updated_at
+ updated_at2 = issue2.updated_at
+
+ Timecop.travel(1.minute.from_now) do
+ described_class.new(parent, user, params).execute(issue)
+ end
+
+ expect(issue.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0)
+ expect(issue1.reload.updated_at.change(usec: 0)).to eq updated_at1.change(usec: 0)
+ expect(issue2.reload.updated_at.change(usec: 0)).to eq updated_at2.change(usec: 0)
+ end
+
if group
context 'when on a group board' do
it 'sends the board_group_id parameter' do
@@ -114,5 +150,13 @@ shared_examples 'issues move service' do |group|
end
end
end
+
+ def reorder_issues(params, issues: [])
+ issues.each do |issue|
+ issue.move_to_end && issue.save!
+ end
+
+ params.merge!(move_after_id: issues[1].id, move_before_id: issues[2].id)
+ end
end
end
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
index 19794227d9f..92c094f08a4 100644
--- a/spec/tasks/gitlab/cleanup_rake_spec.rb
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -156,4 +156,33 @@ describe 'gitlab:cleanup rake tasks' do
end
end
end
+
+ describe 'gitlab:cleanup:orphan_job_artifact_files' do
+ subject(:rake_task) { run_rake_task('gitlab:cleanup:orphan_job_artifact_files') }
+
+ it 'runs the task without errors' do
+ expect(Gitlab::Cleanup::OrphanJobArtifactFiles)
+ .to receive(:new).and_call_original
+
+ expect { rake_task }.not_to raise_error
+ end
+
+ context 'with DRY_RUN set to false' do
+ before do
+ stub_env('DRY_RUN', 'false')
+ end
+
+ it 'passes dry_run correctly' do
+ expect(Gitlab::Cleanup::OrphanJobArtifactFiles)
+ .to receive(:new)
+ .with(limit: anything,
+ dry_run: false,
+ niceness: anything,
+ logger: anything)
+ .and_call_original
+
+ rake_task
+ end
+ end
+ end
end
diff --git a/spec/tasks/migrate/schema_check_rake_spec.rb b/spec/tasks/migrate/schema_check_rake_spec.rb
new file mode 100644
index 00000000000..1097a43cd8a
--- /dev/null
+++ b/spec/tasks/migrate/schema_check_rake_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rake'
+
+describe 'schema_version_check rake task' do
+ include StubENV
+
+ before :all do
+ Rake.application.rake_require 'active_record/railties/databases'
+ Rake.application.rake_require 'tasks/migrate/schema_check'
+
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ before do
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(Gitlab::Database::MIN_SCHEMA_VERSION)
+
+ # Ensure our check can re-run each time
+ Rake::Task[:schema_version_check].reenable
+ end
+
+ it 'allows migrations on databases meeting the min schema version requirement' do
+ expect { run_rake_task('schema_version_check') }.not_to raise_error
+ end
+
+ it 'raises an error when schema version is too old to migrate' do
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(25)
+ expect { run_rake_task('schema_version_check') }.to raise_error(RuntimeError, /current database version is too old to be migrated/)
+ end
+
+ it 'skips running validation when passed the skip env variable' do
+ stub_env('SKIP_SCHEMA_VERSION_CHECK', 'true')
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(25)
+ expect { run_rake_task('schema_version_check') }.not_to raise_error
+ end
+
+ it 'allows migrations on fresh databases' do
+ allow(ActiveRecord::Migrator).to receive(:current_version).and_return(0)
+ expect { run_rake_task('schema_version_check') }.not_to raise_error
+ end
+
+ def run_rake_task(task_name)
+ Rake::Task[task_name].reenable
+ Rake.application.invoke_task task_name
+ end
+end
diff --git a/spec/views/projects/services/_form.haml_spec.rb b/spec/views/projects/services/_form.haml_spec.rb
index 85167bca115..06e159f103b 100644
--- a/spec/views/projects/services/_form.haml_spec.rb
+++ b/spec/views/projects/services/_form.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'projects/services/_form' do
expect(rendered).to have_content('Event will be triggered when a merge request is created/updated/merged')
end
- context 'when service is JIRA' do
+ context 'when service is Jira' do
let(:project) { create(:jira_project) }
before do
@@ -38,8 +38,8 @@ describe 'projects/services/_form' do
it 'display merge_request_events and commit_events descriptions' do
render
- expect(rendered).to have_content('JIRA comments will be created when an issue gets referenced in a commit.')
- expect(rendered).to have_content('JIRA comments will be created when an issue gets referenced in a merge request.')
+ expect(rendered).to have_content('Jira comments will be created when an issue gets referenced in a commit.')
+ expect(rendered).to have_content('Jira comments will be created when an issue gets referenced in a merge request.')
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 1f1b35eed0a..ed9f31f60a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -700,21 +700,22 @@
dependencies:
requireindex "~1.1.0"
-"@gitlab/svgs@^1.65.0":
- version "1.65.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.65.0.tgz#48a3a64c0b5524de4e57d51b82a71274af17744d"
- integrity sha512-GC9JgVu4/2Ysc3hKFmX6TQV6tqvHZDcfd/DzBzYjy3rHO9qYMZFnw/CKCGa8LkU9F79vfDo3G8NSja7FDXMccw==
+"@gitlab/svgs@^1.66.0":
+ version "1.66.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.66.0.tgz#3c02da455421ea241f32e915671842435df027ff"
+ integrity sha512-nxOoQPnofMs3BjRr3SVzQcclM0G6QFrLM8L4nnUCN+8Gxq2u8ukfSU5FCrkivXz+FP9Qo/FYilWV7CY8kDkt6A==
-"@gitlab/ui@^4.3.0":
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-4.3.0.tgz#7d6e626e7143febef642a0418ceae1923dfa8f15"
- integrity sha512-+r19vg9KkFhYO1mlC1Lz98ZnXEifJSumfIUvaRFPgw+LtU2SyOsxXp9fleaJVXXBgpQDiNnpe1FyRL0kWVcn3g==
+"@gitlab/ui@^5.1.0":
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.1.0.tgz#b8c8f266edc68f616e92c0ba3a18a692002393e4"
+ integrity sha512-IPgk5W7mSXcbni+zNuJeVU89Co72jSQAXTxU7AtmItt5XT6nI9US2ZAWNUl8XCiOOw81jzYv0PLp4bMiXdLkww==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
bootstrap "4.3.1"
- bootstrap-vue "^2.0.0-rc.11"
+ bootstrap-vue "^2.0.0-rc.24"
copy-to-clipboard "^3.0.8"
+ core-js "^2.6.9"
echarts "^4.2.0-rc.2"
highlight.js "^9.13.1"
js-beautify "^1.8.8"
@@ -887,6 +888,15 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
+"@nuxt/opencollective@^0.2.2":
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/@nuxt/opencollective/-/opencollective-0.2.2.tgz#17adc7d380457379cd14cbb64a435ea196cc4a6e"
+ integrity sha512-ie50SpS47L+0gLsW4yP23zI/PtjsDRglyozX2G09jeiUazC1AJlGPZo0JUs9iuCDUoIgsDEf66y7/bSfig0BpA==
+ dependencies:
+ chalk "^2.4.1"
+ consola "^2.3.0"
+ node-fetch "^2.3.0"
+
"@sindresorhus/is@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
@@ -1322,11 +1332,6 @@ ansi-colors@^3.0.0:
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.0.5.tgz#cb9dc64993b64fd6945485f797fc3853137d9a7b"
integrity sha512-VVjWpkfaphxUBFarydrQ3n26zX5nIK7hcbT3/ielrvwDDyBBjuh2vuSw1P9zkPq0cfqvdw7lkYHnu+OLSfIBsg==
-ansi-escapes@^1.1.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
- integrity sha1-06ioOzGapneTZisT52HHkRQiMG4=
-
ansi-escapes@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
@@ -1766,15 +1771,6 @@ babel-plugin-rewire@^1.2.0:
resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89"
integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ==
-babel-polyfill@6.23.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d"
- integrity sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=
- dependencies:
- babel-runtime "^6.22.0"
- core-js "^2.4.0"
- regenerator-runtime "^0.10.0"
-
babel-preset-jest@^24.6.0:
version "24.6.0"
resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz#66f06136eefce87797539c0d63f1769cc3915984"
@@ -1783,14 +1779,6 @@ babel-preset-jest@^24.6.0:
"@babel/plugin-syntax-object-rest-spread" "^7.0.0"
babel-plugin-jest-hoist "^24.6.0"
-babel-runtime@^6.22.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
- integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
- dependencies:
- core-js "^2.4.0"
- regenerator-runtime "^0.11.0"
-
babylon@7.0.0-beta.19:
version "7.0.0-beta.19"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
@@ -1933,19 +1921,19 @@ bonjour@^3.5.0:
multicast-dns "^6.0.1"
multicast-dns-service-types "^1.1.0"
-bootstrap-vue@^2.0.0-rc.11:
- version "2.0.0-rc.11"
- resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.11.tgz#47aaa6d2a8d390477de75e636d8ea652b1d03f59"
- integrity sha512-LxR+oL8yKr1DVoWUWTX+XhiT0xaTMH6142u2VSFDm4tewTH8HIrzN2YIl7HLZrw2DIuE9bRMIdWJqqn3aQe7Hw==
+bootstrap-vue@^2.0.0-rc.24:
+ version "2.0.0-rc.24"
+ resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.24.tgz#8ea5bbcd19e0f9b4f87ed4d9ba72abaa35231f32"
+ integrity sha512-8rA/I9tOvpNVIuMKD3rdlrUqgVdPEw4vPI0X8OeFJcG2hHvCHeZDF7FmWqxSeehIrUHGDV17HlTGSuP/v1Sp5g==
dependencies:
- bootstrap "^4.1.1"
- lodash.get "^4.4.2"
- lodash.startcase "^4.4.0"
- opencollective "^1.0.3"
- popper.js "^1.12.9"
- vue-functional-data-merge "^2.0.5"
+ "@nuxt/opencollective" "^0.2.2"
+ bootstrap "^4.3.1"
+ core-js ">=2.6.5 <3.0.0"
+ popper.js "^1.15.0"
+ portal-vue "^2.1.5"
+ vue-functional-data-merge "^3.1.0"
-bootstrap@4.3.1, bootstrap@^4.1.1, bootstrap@^4.1.3:
+bootstrap@4.3.1, bootstrap@^4.1.3, bootstrap@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
@@ -2310,7 +2298,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.1, chalk@^1.1.3:
+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=
@@ -2350,11 +2338,6 @@ character-reference-invalid@^1.0.0:
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed"
integrity sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==
-chardet@^0.4.0:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
- integrity sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=
-
chardet@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029"
@@ -2739,6 +2722,11 @@ connect@^3.6.0:
parseurl "~1.3.2"
utils-merge "1.0.1"
+consola@^2.3.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/consola/-/consola-2.9.0.tgz#57760e3a65a53ec27337f4add31505802d902278"
+ integrity sha512-34Iue+LRcWbndFIfZc5boNizWlsrRjqIBJZTe591vImgbnq7nx2EzlrLtANj9TH2Fxm7puFJBJAOk5BhvZOddQ==
+
console-browserify@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
@@ -2839,10 +2827,10 @@ core-js@3.0.1:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.1.tgz#1343182634298f7f38622f95e73f54e48ddf4738"
integrity sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==
-core-js@^2.2.0, core-js@^2.4.0:
- version "2.5.7"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
- integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==
+"core-js@>=2.6.5 <3.0.0", core-js@^2.2.0, core-js@^2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
+ integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
core-js@^3.1.3:
version "3.1.3"
@@ -3875,13 +3863,6 @@ encodeurl@~1.0.1, encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
-encoding@^0.1.11:
- version "0.1.12"
- resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
- integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
- dependencies:
- iconv-lite "~0.4.13"
-
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@@ -4480,15 +4461,6 @@ extend@^3.0.0, extend@~3.0.2:
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
-external-editor@^2.0.1:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5"
- integrity sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==
- dependencies:
- chardet "^0.4.0"
- iconv-lite "^0.4.17"
- tmp "^0.0.33"
-
external-editor@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.0.tgz#dc35c48c6f98a30ca27a20e9687d7f3c77704bb6"
@@ -5472,7 +5444,7 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
-iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
+iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.22, iconv-lite@^0.4.4:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -5627,25 +5599,6 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
-inquirer@3.0.6:
- version "3.0.6"
- resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.6.tgz#e04aaa9d05b7a3cb9b0f407d04375f0447190347"
- integrity sha1-4EqqnQW3o8ubD0B9BDdfBEcZA0c=
- dependencies:
- ansi-escapes "^1.1.0"
- chalk "^1.0.0"
- cli-cursor "^2.1.0"
- cli-width "^2.0.0"
- external-editor "^2.0.1"
- figures "^2.0.0"
- lodash "^4.3.0"
- mute-stream "0.0.7"
- run-async "^2.2.0"
- rx "^4.1.0"
- string-width "^2.0.0"
- strip-ansi "^3.0.0"
- through "^2.3.6"
-
inquirer@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.0.tgz#51adcd776f661369dc1e894859c2560a224abdd8"
@@ -5996,7 +5949,7 @@ is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
-is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
+is-stream@^1.0.0, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
@@ -7086,11 +7039,6 @@ lodash.escaperegexp@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=
-lodash.get@^4.4.2:
- version "4.4.2"
- resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
- integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
-
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
@@ -7116,17 +7064,12 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-lodash.startcase@^4.4.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8"
- integrity sha1-lDbjTtJgk+1/+uGTYUQ1CRXZrdg=
-
lodash.upperfirst@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, lodash@^4.5.0, lodash@~4.17.10:
+lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.5.0, lodash@~4.17.10:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@@ -7554,7 +7497,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.1.3, 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=
@@ -7735,13 +7678,10 @@ node-ensure@^0.0.0:
resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=
-node-fetch@1.6.3:
- version "1.6.3"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.6.3.tgz#dc234edd6489982d58e8f0db4f695029abcd8c04"
- integrity sha1-3CNO3WSJmC1Y6PDbT2lQKavNjAQ=
- dependencies:
- encoding "^0.1.11"
- is-stream "^1.0.1"
+node-fetch@^2.3.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
+ integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
node-forge@0.6.33:
version "0.6.33"
@@ -8093,31 +8033,11 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
-opencollective@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/opencollective/-/opencollective-1.0.3.tgz#aee6372bc28144583690c3ca8daecfc120dd0ef1"
- integrity sha1-ruY3K8KBRFg2kMPKja7PwSDdDvE=
- dependencies:
- babel-polyfill "6.23.0"
- chalk "1.1.3"
- inquirer "3.0.6"
- minimist "1.2.0"
- node-fetch "1.6.3"
- opn "4.0.2"
-
opener@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
-opn@4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/opn/-/opn-4.0.2.tgz#7abc22e644dff63b0a96d5ab7f2790c0f01abc95"
- integrity sha1-erwi5kTf9jsKltWrfyeQwPAavJU=
- dependencies:
- object-assign "^4.0.1"
- pinkie-promise "^2.0.0"
-
opn@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225"
@@ -8583,10 +8503,15 @@ 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.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==
+popper.js@^1.14.7, popper.js@^1.15.0:
+ version "1.15.0"
+ resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2"
+ integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==
+
+portal-vue@^2.1.5:
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.5.tgz#ecd0997cb32958205151cb72f40fd4f38d175e5c"
+ integrity sha512-vZmdMn0mOo7puvxoMQ5zju6S29aFD+9yygJxyWQtPaMXS9xunAeoYdnx6yzfL9J8HD8pMZYgSieEIbioAKhrSQ==
portfinder@^1.0.9:
version "1.0.13"
@@ -9277,16 +9202,6 @@ regenerate@^1.2.1, regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
-regenerator-runtime@^0.10.0:
- version "0.10.5"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
- integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=
-
-regenerator-runtime@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
- integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
-
regenerator-transform@^0.13.4:
version "0.13.4"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb"
@@ -9656,11 +9571,6 @@ rw@1:
resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
-rx@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782"
- integrity sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=
-
rxjs@^6.1.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.1.tgz#246cebec189a6cbc143a3ef9f62d6f4c91813ca1"
@@ -11418,10 +11328,10 @@ vue-eslint-parser@^4.0.2:
esquery "^1.0.1"
lodash "^4.17.11"
-vue-functional-data-merge@^2.0.5:
- version "2.0.6"
- resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-2.0.6.tgz#f08055adfb92458debcf2ad10c3aa712277f7fc2"
- integrity sha512-eivElFOJwhXJopKlq71/8onDxOKK4quPwWGFF9yIVjpU2sNzxISRpufu18bh674ivSADuEAPU2OhT+vrH0E9Mg==
+vue-functional-data-merge@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz#08a7797583b7f35680587f8a1d51d729aa1dc657"
+ integrity sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA==
vue-hot-reload-api@^2.3.0:
version "2.3.0"